"use strict";;
const {
    __DEV__
} = require("@apollo/client/utilities/environment");

Object.defineProperty(exports, "__esModule", { value: true });
exports.ObservableQuery = void 0;
exports.logMissingFieldErrors = logMissingFieldErrors;
const equality_1 = require("@wry/equality");
const optimism_1 = require("optimism");
const rxjs_1 = require("rxjs");
const utilities_1 = require("@apollo/client/utilities");
const environment_1 = require("@apollo/client/utilities/environment");
const internal_1 = require("@apollo/client/utilities/internal");
const invariant_1 = require("@apollo/client/utilities/invariant");
const equalByQuery_js_1 = require("./equalByQuery.cjs");
const networkStatus_js_1 = require("./networkStatus.cjs");
const { assign, hasOwnProperty } = Object;
const newNetworkStatusSymbol = Symbol();
const uninitialized = {};
class ObservableQuery {
    /**
     * @internal
     * A slot used by the `useQuery` hook to indicate that `client.watchQuery`
     * should not register the query immediately, but instead wait for the query to
     * be started registered with the `QueryManager` when `useSyncExternalStore`
     * actively subscribes to it.
     */
    static inactiveOnCreation = new optimism_1.Slot();
    options;
    queryId;
    queryName;
    // The `query` computed property will always reflect the document transformed
    // by the last run query. `this.options.query` will always reflect the raw
    // untransformed query to ensure document transforms with runtime conditionals
    // are run on the original document.
    get query() {
        return this.lastQuery || this.options.query;
    }
    // Computed shorthand for this.options.variables, preserved for
    // backwards compatibility.
    /**
     * An object containing the variables that were provided for the query.
     */
    get variables() {
        return this.options.variables;
    }
    subject;
    observable;
    isTornDown;
    queryManager;
    subscriptions = new Set();
    waitForOwnResult;
    last;
    lastQuery;
    queryInfo;
    linkSubscription;
    linkObservable;
    pollingInfo;
    networkStatus;
    constructor({ queryManager, queryInfo, options, }) {
        this.networkStatus = networkStatus_js_1.NetworkStatus.loading;
        let startedInactive = ObservableQuery.inactiveOnCreation.getValue();
        this.subject = new rxjs_1.BehaviorSubject(uninitialized);
        this.observable = this.subject.pipe((0, rxjs_1.tap)({
            subscribe: () => {
                if (startedInactive) {
                    queryManager["queries"].set(this.queryId, queryInfo);
                    startedInactive = false;
                }
                if (!this.subject.observed) {
                    if (this.subject.value === uninitialized) {
                        // Emitting a value in the `subscribe` callback of `tap` gives
                        // the subject a chance to save this initial result without
                        // emitting the placeholder value since this callback is executed
                        // before `tap` subscribes to the source observable (the subject).
                        // `reobserve` also has the chance to update this value if it
                        // synchronously emits one (usually due to reporting a cache
                        // value).
                        //
                        // We don't initialize the `BehaviorSubject` with
                        // `getInitialResult` because its possible the cache might have
                        // updated between when the `ObservableQuery` was instantiated and
                        // when it is subscribed to. Updating the value here ensures we
                        // report the most up-to-date result from the cache.
                        this.subject.next(this.getInitialResult());
                    }
                    this.reobserve();
                    // TODO: See if we can rework updatePolling to better handle this.
                    // reobserve calls updatePolling but this `subscribe` callback is
                    // called before the subject is subscribed to so `updatePolling`
                    // can't accurately detect if there is an active subscription.
                    // Calling it again here ensures that it can detect if it can poll
                    setTimeout(() => this.updatePolling());
                }
            },
            unsubscribe: () => {
                if (!this.subject.observed) {
                    this.tearDownQuery();
                }
            },
        }), (0, rxjs_1.filter)((result) => {
            return (this.options.fetchPolicy !== "standby" &&
                (this.options.notifyOnNetworkStatusChange ||
                    !result.loading ||
                    // data could be defined for cache-and-network fetch policies
                    // when emitting the cache result while loading the network result
                    !!result.data));
        }));
        this["@@observable"] = () => this;
        if (Symbol.observable) {
            this[Symbol.observable] = () => this;
        }
        this.pipe = this.observable.pipe.bind(this.observable);
        this.subscribe = this.observable.subscribe.bind(this.observable);
        // related classes
        this.queryInfo = queryInfo;
        this.queryManager = queryManager;
        // active state
        this.waitForOwnResult = skipCacheDataFor(options.fetchPolicy);
        this.isTornDown = false;
        this.subscribe = this.subscribe.bind(this);
        this.subscribeToMore = this.subscribeToMore.bind(this);
        this.maskResult = this.maskResult.bind(this);
        const { watchQuery: { fetchPolicy: defaultFetchPolicy = "cache-first" } = {}, } = queryManager.defaultOptions;
        const { fetchPolicy = defaultFetchPolicy, 
        // Make sure we don't store "standby" as the initialFetchPolicy.
        initialFetchPolicy = fetchPolicy === "standby" ? defaultFetchPolicy : (fetchPolicy), } = options;
        this.options = {
            ...options,
            // Remember the initial options.fetchPolicy so we can revert back to this
            // policy when variables change. This information can also be specified
            // (or overridden) by providing options.initialFetchPolicy explicitly.
            initialFetchPolicy,
            // This ensures this.options.fetchPolicy always has a string value, in
            // case options.fetchPolicy was not provided.
            fetchPolicy,
        };
        this.queryId = queryInfo.queryId || queryManager.generateQueryId();
        const opDef = (0, utilities_1.getOperationDefinition)(this.query);
        this.queryName = opDef && opDef.name && opDef.name.value;
    }
    // We can't use Observable['subscribe'] here as the type as it conflicts with
    // the ability to infer T from Subscribable<T>. This limits the surface area
    // to the non-deprecated signature which works properly with type inference.
    subscribe;
    pipe;
    [Symbol.observable];
    ["@@observable"];
    /** @internal */
    resetDiff() {
        this.queryInfo.resetDiff();
    }
    getInitialResult() {
        const fetchPolicy = this.queryManager.prioritizeCacheValues ?
            "cache-first"
            : this.options.fetchPolicy;
        const defaultResult = {
            data: undefined,
            loading: true,
            networkStatus: networkStatus_js_1.NetworkStatus.loading,
            partial: true,
        };
        const cacheResult = () => {
            const diff = this.queryInfo.getDiff();
            return this.maskResult({
                data: 
                // TODO: queryInfo.getDiff should handle this since cache.diff returns a
                // null when returnPartialData is false
                this.options.returnPartialData || diff.complete ?
                    diff.result ?? undefined
                    : undefined,
                loading: !diff.complete,
                networkStatus: diff.complete ? networkStatus_js_1.NetworkStatus.ready : networkStatus_js_1.NetworkStatus.loading,
                partial: !diff.complete,
            });
        };
        switch (fetchPolicy) {
            case "cache-only":
            case "cache-first":
                return cacheResult();
            case "cache-and-network":
                return {
                    ...cacheResult(),
                    loading: true,
                    networkStatus: networkStatus_js_1.NetworkStatus.loading,
                };
            case "standby":
                return {
                    ...defaultResult,
                    loading: false,
                    networkStatus: networkStatus_js_1.NetworkStatus.ready,
                };
            default:
                return defaultResult;
        }
    }
    getCurrentFullResult(saveAsLastResult = true) {
        // Use the last result as long as the variables match this.variables.
        const lastResult = this.getLastResult(true);
        const networkStatus = this.networkStatus;
        const result = {
            data: undefined,
            partial: true,
            ...lastResult,
            loading: (0, networkStatus_js_1.isNetworkRequestInFlight)(networkStatus),
            networkStatus,
        };
        let { fetchPolicy = "cache-first" } = this.options;
        const { prioritizeCacheValues } = this.queryManager;
        if (prioritizeCacheValues) {
            fetchPolicy = "cache-first";
        }
        if (
        // These fetch policies should never deliver data from the cache, unless
        // redelivering a previously delivered result.
        skipCacheDataFor(fetchPolicy) ||
            // If this.options.query has @client(always: true) fields, we cannot
            // trust diff.result, since it was read from the cache without running
            // local resolvers (and it's too late to run resolvers now, since we must
            // return a result synchronously).
            this.queryManager.getDocumentInfo(this.query).hasForcedResolvers) {
            // Fall through.
        }
        else if (this.waitForOwnResult && !prioritizeCacheValues) {
            // This would usually be a part of `QueryInfo.getDiff()`.
            // which we skip in the waitForOwnResult case since we are not
            // interested in the diff.
            this.queryInfo["updateWatch"]();
        }
        else {
            const diff = this.queryInfo.getDiff();
            result.partial = !diff.complete;
            if (diff.complete || this.options.returnPartialData) {
                result.data = diff.result;
            }
            if (result.data === null) {
                result.data = void 0;
            }
            if (diff.complete) {
                // If the diff is complete, and we're using a FetchPolicy that
                // terminates after a complete cache read, we can assume the next result
                // we receive will have NetworkStatus.ready and !loading.
                if (diff.complete &&
                    result.networkStatus === networkStatus_js_1.NetworkStatus.loading &&
                    (fetchPolicy === "cache-first" || fetchPolicy === "cache-only")) {
                    result.networkStatus = networkStatus_js_1.NetworkStatus.ready;
                    result.loading = false;
                }
            }
            // We need to check for both both `error` and `errors` field because there
            // are cases where sometimes `error` is set, but not `errors` and
            // vice-versa. This will be updated in the next major version when
            // `errors` is deprecated in favor of `error`.
            if (result.networkStatus === networkStatus_js_1.NetworkStatus.ready && result.error) {
                result.networkStatus = networkStatus_js_1.NetworkStatus.error;
            }
            if (environment_1.__DEV__ &&
                !diff.complete &&
                !result.loading &&
                !result.data &&
                !result.error) {
                logMissingFieldErrors(diff.missing);
            }
        }
        if (saveAsLastResult) {
            this.updateLastResult(result);
        }
        return result;
    }
    getCurrentResult(saveAsLastResult = true) {
        return this.maskResult(this.getCurrentFullResult(saveAsLastResult));
    }
    // Compares newResult to the snapshot we took of this.lastResult when it was
    // first received.
    isDifferentFromLastResult(newResult, variables) {
        if (!this.last) {
            return true;
        }
        const documentInfo = this.queryManager.getDocumentInfo(this.query);
        const dataMasking = this.queryManager.dataMasking;
        const query = dataMasking ? documentInfo.nonReactiveQuery : this.query;
        const resultIsDifferent = dataMasking || documentInfo.hasNonreactiveDirective ?
            !(0, equalByQuery_js_1.equalByQuery)(query, this.last.result, newResult, this.variables)
            : !(0, equality_1.equal)(this.last.result, newResult);
        return (resultIsDifferent || (variables && !(0, equality_1.equal)(this.last.variables, variables)));
    }
    getLast(key, variablesMustMatch) {
        const last = this.last;
        if (last &&
            last[key] &&
            (!variablesMustMatch || (0, equality_1.equal)(last.variables, this.variables))) {
            return last[key];
        }
    }
    // TODO: Consider deprecating this function
    getLastResult(variablesMustMatch) {
        return this.getLast("result", variablesMustMatch);
    }
    // TODO: Consider deprecating this function
    getLastError(variablesMustMatch) {
        return this.getLast("error", variablesMustMatch);
    }
    // TODO: Consider deprecating this function
    resetLastResults() {
        delete this.last;
        // TODO: This will need to be removed when tearing down an ObservableQuery
        // since the observable will terminate.
        this.isTornDown = false;
    }
    /**
     * Update the variables of this observable query, and fetch the new results.
     * This method should be preferred over `setVariables` in most use cases.
     *
     * @param variables - The new set of variables. If there are missing variables,
     * the previous values of those variables will be used.
     */
    refetch(variables) {
        const reobserveOptions = {
            // Always disable polling for refetches.
            pollInterval: 0,
        };
        // Unless the provided fetchPolicy always consults the network
        // (no-cache, network-only, or cache-and-network), override it with
        // network-only to force the refetch for this fetchQuery call.
        const { fetchPolicy } = this.options;
        if (fetchPolicy === "no-cache") {
            reobserveOptions.fetchPolicy = "no-cache";
        }
        else {
            reobserveOptions.fetchPolicy = "network-only";
        }
        if (environment_1.__DEV__ && variables && hasOwnProperty.call(variables, "variables")) {
            const queryDef = (0, utilities_1.getQueryDefinition)(this.query);
            const vars = queryDef.variableDefinitions;
            if (!vars || !vars.some((v) => v.variable.name.value === "variables")) {
                __DEV__ && invariant_1.invariant.warn(67, variables, queryDef.name?.value || queryDef);
            }
        }
        if (variables && !(0, equality_1.equal)(this.options.variables, variables)) {
            // Update the existing options with new variables
            reobserveOptions.variables = this.options.variables = {
                ...this.options.variables,
                ...variables,
            };
        }
        this.queryInfo.resetLastWrite();
        return this.reobserve({
            ...reobserveOptions,
            [newNetworkStatusSymbol]: networkStatus_js_1.NetworkStatus.refetch,
        });
    }
    /**
     * A function that helps you fetch the next set of results for a [paginated list field](https://www.apollographql.com/docs/react/pagination/core-api/).
     */
    fetchMore(fetchMoreOptions) {
        const combinedOptions = {
            ...(fetchMoreOptions.query ? fetchMoreOptions : ({
                ...this.options,
                query: this.options.query,
                ...fetchMoreOptions,
                variables: {
                    ...this.options.variables,
                    ...fetchMoreOptions.variables,
                },
            })),
            // The fetchMore request goes immediately to the network and does
            // not automatically write its result to the cache (hence no-cache
            // instead of network-only), because we allow the caller of
            // fetchMore to provide an updateQuery callback that determines how
            // the data gets written to the cache.
            fetchPolicy: "no-cache",
            notifyOnNetworkStatusChange: this.options.notifyOnNetworkStatusChange,
        };
        combinedOptions.query = this.transformDocument(combinedOptions.query);
        const qid = this.queryManager.generateQueryId();
        // If a temporary query is passed to `fetchMore`, we don't want to store
        // it as the last query result since it may be an optimized query for
        // pagination. We will however run the transforms on the original document
        // as well as the document passed in `fetchMoreOptions` to ensure the cache
        // uses the most up-to-date document which may rely on runtime conditionals.
        this.lastQuery =
            fetchMoreOptions.query ?
                this.transformDocument(this.options.query)
                : combinedOptions.query;
        // Simulate a loading result for the original query with
        // result.networkStatus === NetworkStatus.fetchMore.
        const originalNetworkStatus = this.networkStatus;
        this.networkStatus = networkStatus_js_1.NetworkStatus.fetchMore;
        if (combinedOptions.notifyOnNetworkStatusChange) {
            this.observe();
        }
        const updatedQuerySet = new Set();
        const updateQuery = fetchMoreOptions?.updateQuery;
        const isCached = this.options.fetchPolicy !== "no-cache";
        if (!isCached) {
            (0, invariant_1.invariant)(updateQuery, 68);
        }
        return this.queryManager
            .fetchQuery(qid, combinedOptions, networkStatus_js_1.NetworkStatus.fetchMore)
            .then((fetchMoreResult) => {
            this.queryManager.removeQuery(qid);
            if (this.networkStatus === networkStatus_js_1.NetworkStatus.fetchMore) {
                this.networkStatus = originalNetworkStatus;
            }
            if (isCached) {
                // Performing this cache update inside a cache.batch transaction ensures
                // any affected cache.watch watchers are notified at most once about any
                // updates. Most watchers will be using the QueryInfo class, which
                // responds to notifications by calling reobserveCacheFirst to deliver
                // fetchMore cache results back to this ObservableQuery.
                this.queryManager.cache.batch({
                    update: (cache) => {
                        const { updateQuery } = fetchMoreOptions;
                        if (updateQuery) {
                            cache.updateQuery({
                                query: this.query,
                                variables: this.variables,
                                returnPartialData: true,
                                optimistic: false,
                            }, (previous) => updateQuery(previous, {
                                fetchMoreResult: fetchMoreResult.data,
                                variables: combinedOptions.variables,
                            }));
                        }
                        else {
                            // If we're using a field policy instead of updateQuery, the only
                            // thing we need to do is write the new data to the cache using
                            // combinedOptions.variables (instead of this.variables, which is
                            // what this.updateQuery uses, because it works by abusing the
                            // original field value, keyed by the original variables).
                            cache.writeQuery({
                                query: combinedOptions.query,
                                variables: combinedOptions.variables,
                                data: fetchMoreResult.data,
                            });
                        }
                    },
                    onWatchUpdated: (watch) => {
                        // Record the DocumentNode associated with any watched query whose
                        // data were updated by the cache writes above.
                        updatedQuerySet.add(watch.query);
                    },
                });
            }
            else {
                // There is a possibility `lastResult` may not be set when
                // `fetchMore` is called which would cause this to crash. This should
                // only happen if we haven't previously reported a result. We don't
                // quite know what the right behavior should be here since this block
                // of code runs after the fetch result has executed on the network.
                // We plan to let it crash in the meantime.
                //
                // If we get bug reports due to the `data` property access on
                // undefined, this should give us a real-world scenario that we can
                // use to test against and determine the right behavior. If we do end
                // up changing this behavior, this may require, for example, an
                // adjustment to the types on `updateQuery` since that function
                // expects that the first argument always contains previous result
                // data, but not `undefined`.
                const lastResult = this.getLast("result");
                const data = updateQuery(lastResult.data, {
                    fetchMoreResult: fetchMoreResult.data,
                    variables: combinedOptions.variables,
                });
                this.reportResult({
                    ...lastResult,
                    networkStatus: originalNetworkStatus,
                    loading: (0, networkStatus_js_1.isNetworkRequestInFlight)(originalNetworkStatus),
                    data: data,
                }, this.variables);
            }
            return this.maskResult(fetchMoreResult);
        })
            .finally(() => {
            // In case the cache writes above did not generate a broadcast
            // notification (which would have been intercepted by onWatchUpdated),
            // likely because the written data were the same as what was already in
            // the cache, we still want fetchMore to deliver its final loading:false
            // result with the unchanged data.
            if (isCached && !updatedQuerySet.has(this.query)) {
                this.reobserveCacheFirst();
            }
        });
    }
    // XXX the subscription variables are separate from the query variables.
    // if you want to update subscription variables, right now you have to do that separately,
    // and you can only do it by stopping the subscription and then subscribing again with new variables.
    /**
     * A function that enables you to execute a [subscription](https://www.apollographql.com/docs/react/data/subscriptions/), usually to subscribe to specific fields that were included in the query.
     *
     * This function returns _another_ function that you can call to terminate the subscription.
     */
    subscribeToMore(options) {
        const subscription = this.queryManager
            .startGraphQLSubscription({
            query: options.document,
            variables: options.variables,
            context: options.context,
        })
            .subscribe({
            next: (subscriptionData) => {
                const { updateQuery, onError } = options;
                const { error } = subscriptionData;
                if (error) {
                    if (onError) {
                        onError(error);
                    }
                    else {
                        __DEV__ && invariant_1.invariant.error(69, error);
                    }
                    return;
                }
                if (updateQuery) {
                    this.updateQuery((previous, updateOptions) => updateQuery(previous, {
                        subscriptionData: subscriptionData,
                        ...updateOptions,
                    }));
                }
            },
        });
        this.subscriptions.add(subscription);
        return () => {
            if (this.subscriptions.delete(subscription)) {
                subscription.unsubscribe();
            }
        };
    }
    /** @internal */
    silentSetOptions(newOptions) {
        const mergedOptions = (0, utilities_1.compact)(this.options, newOptions || {});
        assign(this.options, mergedOptions);
    }
    /**
     * Update the variables of this observable query, and fetch the new results
     * if they've changed. Most users should prefer `refetch` instead of
     * `setVariables` in order to to be properly notified of results even when
     * they come from the cache.
     *
     * Note: the `next` callback will *not* fire if the variables have not changed
     * or if the result is coming from cache.
     *
     * Note: the promise will return the old results immediately if the variables
     * have not changed.
     *
     * Note: the promise will return the last result immediately if the query is not active
     * (there are no subscribers).
     *
     * @param variables - The new set of variables. If there are missing variables,
     * the previous values of those variables will be used.
     */
    async setVariables(variables) {
        if ((0, equality_1.equal)(this.variables, variables)) {
            // If we have no observers, then we don't actually want to make a network
            // request. As soon as someone observes the query, the request will kick
            // off. For now, we just store any changes. (See #1077)
            return (0, internal_1.toQueryResult)(this.subject.getValue());
        }
        this.options.variables = variables;
        // See comment above
        if (!this.hasObservers()) {
            return (0, internal_1.toQueryResult)(this.subject.getValue());
        }
        return this.reobserve({
            // Reset options.fetchPolicy to its original value.
            fetchPolicy: this.options.initialFetchPolicy,
            variables,
            [newNetworkStatusSymbol]: networkStatus_js_1.NetworkStatus.setVariables,
        });
    }
    /**
     * A function that enables you to update the query's cached result without executing a followup GraphQL operation.
     *
     * See [using updateQuery and updateFragment](https://www.apollographql.com/docs/react/caching/cache-interaction/#using-updatequery-and-updatefragment) for additional information.
     */
    updateQuery(mapFn) {
        const { queryManager } = this;
        const { result, complete } = queryManager.cache.diff({
            query: this.options.query,
            variables: this.variables,
            returnPartialData: true,
            optimistic: false,
        });
        const newResult = mapFn(result, {
            variables: this.variables,
            complete: !!complete,
            previousData: result,
        });
        if (newResult) {
            queryManager.cache.writeQuery({
                query: this.options.query,
                data: newResult,
                variables: this.variables,
            });
            queryManager.broadcastQueries();
        }
    }
    /**
     * A function that instructs the query to begin re-executing at a specified interval (in milliseconds).
     */
    startPolling(pollInterval) {
        this.options.pollInterval = pollInterval;
        this.updatePolling();
    }
    /**
     * A function that instructs the query to stop polling after a previous call to `startPolling`.
     */
    stopPolling() {
        this.options.pollInterval = 0;
        this.updatePolling();
    }
    // Update options.fetchPolicy according to options.nextFetchPolicy.
    applyNextFetchPolicy(reason, 
    // It's possible to use this method to apply options.nextFetchPolicy to
    // options.fetchPolicy even if options !== this.options, though that happens
    // most often when the options are temporary, used for only one request and
    // then thrown away, so nextFetchPolicy may not end up mattering.
    options) {
        if (options.nextFetchPolicy) {
            const { fetchPolicy = "cache-first", initialFetchPolicy = fetchPolicy } = options;
            if (fetchPolicy === "standby") {
                // Do nothing, leaving options.fetchPolicy unchanged.
            }
            else if (typeof options.nextFetchPolicy === "function") {
                // When someone chooses "cache-and-network" or "network-only" as their
                // initial FetchPolicy, they often do not want future cache updates to
                // trigger unconditional network requests, which is what repeatedly
                // applying the "cache-and-network" or "network-only" policies would
                // seem to imply. Instead, when the cache reports an update after the
                // initial network request, it may be desirable for subsequent network
                // requests to be triggered only if the cache result is incomplete. To
                // that end, the options.nextFetchPolicy option provides an easy way to
                // update options.fetchPolicy after the initial network request, without
                // having to call observableQuery.reobserve.
                options.fetchPolicy = options.nextFetchPolicy(fetchPolicy, {
                    reason,
                    options,
                    observable: this,
                    initialFetchPolicy,
                });
            }
            else if (reason === "variables-changed") {
                options.fetchPolicy = initialFetchPolicy;
            }
            else {
                options.fetchPolicy = options.nextFetchPolicy;
            }
        }
        return options.fetchPolicy;
    }
    fetch(options, newNetworkStatus, emitLoadingState, query) {
        // TODO Make sure we update the networkStatus (and infer fetchVariables)
        // before actually committing to the fetch.
        const queryInfo = this.queryManager.getOrCreateQuery(this.queryId);
        queryInfo.setObservableQuery(this);
        return this.queryManager.fetchObservableWithInfo(queryInfo, options, newNetworkStatus, query, emitLoadingState);
    }
    // Turns polling on or off based on this.options.pollInterval.
    updatePolling() {
        // Avoid polling in SSR mode
        if (this.queryManager.ssrMode) {
            return;
        }
        const { pollingInfo, options: { pollInterval }, } = this;
        if (!pollInterval || !this.hasObservers()) {
            this.cancelPolling();
            return;
        }
        if (pollingInfo && pollingInfo.interval === pollInterval) {
            return;
        }
        (0, invariant_1.invariant)(pollInterval, 70);
        const info = pollingInfo || (this.pollingInfo = {});
        info.interval = pollInterval;
        const maybeFetch = () => {
            if (this.pollingInfo) {
                if (!(0, networkStatus_js_1.isNetworkRequestInFlight)(this.networkStatus) &&
                    !this.options.skipPollAttempt?.()) {
                    this.reobserve({
                        // Most fetchPolicy options don't make sense to use in a polling context, as
                        // users wouldn't want to be polling the cache directly. However, network-only and
                        // no-cache are both useful for when the user wants to control whether or not the
                        // polled results are written to the cache.
                        fetchPolicy: this.options.initialFetchPolicy === "no-cache" ?
                            "no-cache"
                            : "network-only",
                        [newNetworkStatusSymbol]: networkStatus_js_1.NetworkStatus.poll,
                    }).then(poll, poll);
                }
                else {
                    poll();
                }
            }
        };
        const poll = () => {
            const info = this.pollingInfo;
            if (info) {
                clearTimeout(info.timeout);
                info.timeout = setTimeout(maybeFetch, info.interval);
            }
        };
        poll();
    }
    // This differs from stopPolling in that it does not set pollInterval to 0
    cancelPolling() {
        if (this.pollingInfo) {
            clearTimeout(this.pollingInfo.timeout);
            delete this.pollingInfo;
        }
    }
    updateLastResult(newResult, variables = this.variables) {
        let error = this.getLastError();
        // Preserve this.last.error unless the variables have changed.
        if (error && this.last && !(0, equality_1.equal)(variables, this.last.variables)) {
            error = void 0;
        }
        return (this.last = {
            result: this.queryManager.assumeImmutableResults ?
                newResult
                : (0, utilities_1.cloneDeep)(newResult),
            variables,
            ...(error ? { error } : null),
        });
    }
    /**
     * Reevaluate the query, optionally against new options. New options will be
     * merged with the current options when given.
     */
    reobserve(newOptions) {
        this.isTornDown = false;
        let newNetworkStatus;
        if (newOptions) {
            newNetworkStatus = newOptions[newNetworkStatusSymbol];
            // Avoid setting the symbol option in this.options
            delete newOptions[newNetworkStatusSymbol];
        }
        const useDisposableObservable = 
        // Refetching uses a disposable Observable to allow refetches using different
        // options/variables, without permanently altering the options of the
        // original ObservableQuery.
        newNetworkStatus === networkStatus_js_1.NetworkStatus.refetch ||
            // The fetchMore method does not actually call the reobserve method, but,
            // if it did, it would definitely use a disposable Observable.
            newNetworkStatus === networkStatus_js_1.NetworkStatus.fetchMore ||
            // Polling uses a disposable Observable so the polling options (which force
            // fetchPolicy to be "network-only" or "no-cache") won't override the original options.
            newNetworkStatus === networkStatus_js_1.NetworkStatus.poll;
        // Save the old variables, since Object.assign may modify them below.
        const oldVariables = this.options.variables;
        const oldFetchPolicy = this.options.fetchPolicy;
        const mergedOptions = (0, utilities_1.compact)(this.options, newOptions || {});
        const options = useDisposableObservable ?
            // Disposable Observable fetches receive a shallow copy of this.options
            // (merged with newOptions), leaving this.options unmodified.
            mergedOptions
            : assign(this.options, mergedOptions);
        // Don't update options.query with the transformed query to avoid
        // overwriting this.options.query when we aren't using a disposable concast.
        // We want to ensure we can re-run the custom document transforms the next
        // time a request is made against the original query.
        const query = this.transformDocument(options.query);
        const { fetchPolicy } = options;
        this.lastQuery = query;
        if (!useDisposableObservable) {
            // We can skip calling updatePolling if we're not changing this.options.
            this.updatePolling();
            // Reset options.fetchPolicy to its original value when variables change,
            // unless a new fetchPolicy was provided by newOptions.
            if (newOptions &&
                newOptions.variables &&
                !(0, equality_1.equal)(newOptions.variables, oldVariables) &&
                // Don't mess with the fetchPolicy if it's currently "standby".
                fetchPolicy !== "standby" &&
                // If we're changing the fetchPolicy anyway, don't try to change it here
                // using applyNextFetchPolicy. The explicit options.fetchPolicy wins.
                (fetchPolicy === oldFetchPolicy ||
                    // A `nextFetchPolicy` function has even higher priority, though,
                    // so in that case `applyNextFetchPolicy` must be called.
                    typeof options.nextFetchPolicy === "function")) {
                this.applyNextFetchPolicy("variables-changed", options);
                if (newNetworkStatus === void 0) {
                    newNetworkStatus = networkStatus_js_1.NetworkStatus.setVariables;
                }
            }
        }
        const oldNetworkStatus = this.networkStatus;
        if (!newNetworkStatus) {
            newNetworkStatus = networkStatus_js_1.NetworkStatus.loading;
            if (oldNetworkStatus !== networkStatus_js_1.NetworkStatus.loading &&
                newOptions?.variables &&
                !(0, equality_1.equal)(newOptions.variables, oldVariables)) {
                newNetworkStatus = networkStatus_js_1.NetworkStatus.setVariables;
            }
            // QueryManager does not emit any values for standby fetch policies so we
            // want ensure that the networkStatus remains ready.
            if (fetchPolicy === "standby") {
                newNetworkStatus = networkStatus_js_1.NetworkStatus.ready;
            }
        }
        if (fetchPolicy === "standby") {
            this.cancelPolling();
        }
        this.networkStatus = newNetworkStatus;
        this.waitForOwnResult &&= skipCacheDataFor(options.fetchPolicy);
        const finishWaitingForOwnResult = () => {
            if (this.linkObservable === observable) {
                this.waitForOwnResult = false;
            }
        };
        const variables = options.variables && { ...options.variables };
        const { notifyOnNetworkStatusChange = true } = options;
        const { observable, fromLink } = this.fetch(options, newNetworkStatus, notifyOnNetworkStatusChange &&
            oldNetworkStatus !== newNetworkStatus &&
            (0, networkStatus_js_1.isNetworkRequestInFlight)(newNetworkStatus), query);
        const observer = {
            next: (result) => {
                if ((0, equality_1.equal)(this.variables, variables)) {
                    finishWaitingForOwnResult();
                    this.reportResult(result, variables);
                }
            },
            error: (error) => {
                if ((0, equality_1.equal)(this.variables, variables)) {
                    finishWaitingForOwnResult();
                    this.reportError(error, variables);
                }
            },
        };
        if (!useDisposableObservable && (fromLink || !this.linkSubscription)) {
            if (this.linkSubscription) {
                this.linkSubscription.unsubscribe();
            }
            this.linkObservable = observable;
            this.linkSubscription = observable.subscribe(observer);
        }
        else {
            observable.subscribe(observer);
        }
        return (0, utilities_1.preventUnhandledRejection)(
        // Note: lastValueFrom will create a separate subscription to the
        // observable which means that terminating this ObservableQuery will not
        // cancel the request from the link chain.
        (0, rxjs_1.lastValueFrom)(observable, {
            // This default value should only be used when using a `fetchPolicy` of
            // `standby` since that fetch policy completes without emitting a
            // result. Since we are converting this to a QueryResult type, we
            // omit the extra fields from ApolloQueryResult in the default value.
            defaultValue: { data: undefined },
        }).then((result) => (0, internal_1.toQueryResult)(this.maskResult(result))));
    }
    // (Re)deliver the current result to this.observers without applying fetch
    // policies or making network requests.
    observe() {
        this.reportResult(
        // Passing false is important so that this.getCurrentResult doesn't
        // save the fetchMore result as this.lastResult, causing it to be
        // ignored due to the this.isDifferentFromLastResult check in
        // this.reportResult.
        this.getCurrentFullResult(false), this.variables);
    }
    reportResult(result, variables) {
        const lastError = this.getLastError();
        const isDifferent = this.isDifferentFromLastResult(result, variables);
        // Update the last result even when isDifferentFromLastResult returns false,
        // because the query may be using the @nonreactive directive, and we want to
        // save the the latest version of any nonreactive subtrees (in case
        // getCurrentResult is called), even though we skip broadcasting changes.
        this.updateLastResult(result, variables);
        this.networkStatus = result.networkStatus;
        if (lastError || isDifferent) {
            this.subject.next(this.maskResult(result));
        }
    }
    reportError(error, variables) {
        // Since we don't get the current result on errors, only the error, we
        // must mirror the updates that occur in QueryStore.markQueryError here
        const errorResult = {
            data: undefined,
            partial: true,
            ...this.getLastResult(),
            error,
            networkStatus: networkStatus_js_1.NetworkStatus.error,
            loading: false,
        };
        this.updateLastResult(errorResult, variables);
        this.networkStatus = networkStatus_js_1.NetworkStatus.error;
        this.last.error = error;
        this.subject.next(errorResult);
    }
    hasObservers() {
        return this.subject.observed;
    }
    tearDownQuery() {
        if (this.isTornDown)
            return;
        if (this.linkObservable && this.linkSubscription) {
            this.linkSubscription.unsubscribe();
            delete this.linkObservable;
            delete this.linkSubscription;
        }
        this.stopPolling();
        // stop all active GraphQL subscriptions
        this.subscriptions.forEach((sub) => sub.unsubscribe());
        this.subscriptions.clear();
        this.queryManager.stopQuery(this.queryId);
        this.isTornDown = true;
    }
    transformDocument(document) {
        return this.queryManager.transform(document);
    }
    maskResult(result) {
        return result && "data" in result ?
            {
                ...result,
                data: this.queryManager.maskOperation({
                    document: this.query,
                    data: result.data,
                    fetchPolicy: this.options.fetchPolicy,
                    id: this.queryId,
                }),
            }
            : result;
    }
    dirty = false;
    notifyTimeout;
    /** @internal */
    resetNotifications() {
        this.cancelNotifyTimeout();
        this.dirty = false;
    }
    cancelNotifyTimeout() {
        if (this.notifyTimeout) {
            clearTimeout(this.notifyTimeout);
            this.notifyTimeout = void 0;
        }
    }
    /** @internal */
    scheduleNotify() {
        if (this.dirty)
            return;
        this.dirty = true;
        if (!this.notifyTimeout) {
            this.notifyTimeout = setTimeout(() => this.notify(), 0);
        }
    }
    /** @internal */
    notify() {
        this.cancelNotifyTimeout();
        if (this.dirty) {
            if (this.options.fetchPolicy == "cache-only" ||
                this.options.fetchPolicy == "cache-and-network" ||
                !(0, networkStatus_js_1.isNetworkRequestInFlight)(this.networkStatus)) {
                const diff = this.queryInfo.getDiff();
                if (diff.fromOptimisticTransaction) {
                    // If this diff came from an optimistic transaction, deliver the
                    // current cache data to the ObservableQuery, but don't perform a
                    // reobservation, since oq.reobserveCacheFirst might make a network
                    // request, and we never want to trigger network requests in the
                    // middle of optimistic updates.
                    this.observe();
                }
                else {
                    // Otherwise, make the ObservableQuery "reobserve" the latest data
                    // using a temporary fetch policy of "cache-first", so complete cache
                    // results have a chance to be delivered without triggering additional
                    // network requests, even when options.fetchPolicy is "network-only"
                    // or "cache-and-network". All other fetch policies are preserved by
                    // this method, and are handled by calling oq.reobserve(). If this
                    // reobservation is spurious, isDifferentFromLastResult still has a
                    // chance to catch it before delivery to ObservableQuery subscribers.
                    this.reobserveCacheFirst();
                }
            }
        }
        this.dirty = false;
    }
    // Reobserve with fetchPolicy effectively set to "cache-first", triggering
    // delivery of any new data from the cache, possibly falling back to the network
    // if any cache data are missing. This allows _complete_ cache results to be
    // delivered without also kicking off unnecessary network requests when
    // this.options.fetchPolicy is "cache-and-network" or "network-only". When
    // this.options.fetchPolicy is any other policy ("cache-first", "cache-only",
    // "standby", or "no-cache"), we call this.reobserve() as usual.
    reobserveCacheFirst() {
        const { fetchPolicy, nextFetchPolicy } = this.options;
        if (fetchPolicy === "cache-and-network" || fetchPolicy === "network-only") {
            return this.reobserve({
                fetchPolicy: "cache-first",
                // Use a temporary nextFetchPolicy function that replaces itself with the
                // previous nextFetchPolicy value and returns the original fetchPolicy.
                nextFetchPolicy(currentFetchPolicy, context) {
                    // Replace this nextFetchPolicy function in the options object with the
                    // original this.options.nextFetchPolicy value.
                    this.nextFetchPolicy = nextFetchPolicy;
                    // If the original nextFetchPolicy value was a function, give it a
                    // chance to decide what happens here.
                    if (typeof this.nextFetchPolicy === "function") {
                        return this.nextFetchPolicy(currentFetchPolicy, context);
                    }
                    // Otherwise go back to the original this.options.fetchPolicy.
                    return fetchPolicy;
                },
            });
        }
        return this.reobserve();
    }
}
exports.ObservableQuery = ObservableQuery;
function logMissingFieldErrors(missing) {
    if (environment_1.__DEV__ && missing) {
        __DEV__ && invariant_1.invariant.debug(71, missing);
    }
}
function skipCacheDataFor(fetchPolicy /* `undefined` would mean `"cache-first"` */) {
    return (fetchPolicy === "network-only" ||
        fetchPolicy === "no-cache" ||
        fetchPolicy === "standby");
}
//# sourceMappingURL=ObservableQuery.cjs.map
