"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FragmentReference = void 0;
const equality_1 = require("@wry/equality");
const utilities_1 = require("@apollo/client/utilities");
class FragmentReference {
    observable;
    key = {};
    promise;
    resolve;
    reject;
    subscription;
    listeners = new Set();
    autoDisposeTimeoutId;
    references = 0;
    constructor(client, watchFragmentOptions, options) {
        this.dispose = this.dispose.bind(this);
        this.handleNext = this.handleNext.bind(this);
        this.handleError = this.handleError.bind(this);
        this.observable = client.watchFragment(watchFragmentOptions);
        if (options.onDispose) {
            this.onDispose = options.onDispose;
        }
        const diff = this.getDiff(client, watchFragmentOptions);
        // Start a timer that will automatically dispose of the query if the
        // suspended resource does not use this fragmentRef in the given time. This
        // helps prevent memory leaks when a component has unmounted before the
        // query has finished loading.
        const startDisposeTimer = () => {
            if (!this.references) {
                this.autoDisposeTimeoutId = setTimeout(this.dispose, options.autoDisposeTimeoutMs ?? 30_000);
            }
        };
        this.promise =
            diff.complete ?
                (0, utilities_1.createFulfilledPromise)(diff.result)
                : this.createPendingPromise();
        this.subscribeToFragment();
        this.promise.then(startDisposeTimer, startDisposeTimer);
    }
    listen(listener) {
        this.listeners.add(listener);
        return () => {
            this.listeners.delete(listener);
        };
    }
    retain() {
        this.references++;
        clearTimeout(this.autoDisposeTimeoutId);
        let disposed = false;
        return () => {
            if (disposed) {
                return;
            }
            disposed = true;
            this.references--;
            setTimeout(() => {
                if (!this.references) {
                    this.dispose();
                }
            });
        };
    }
    dispose() {
        this.subscription.unsubscribe();
        this.onDispose();
    }
    onDispose() {
        // noop. overridable by options
    }
    subscribeToFragment() {
        this.subscription = this.observable.subscribe(this.handleNext.bind(this), this.handleError.bind(this));
    }
    handleNext(result) {
        switch (this.promise.status) {
            case "pending": {
                if (result.complete) {
                    return this.resolve?.(result.data);
                }
                this.deliver(this.promise);
                break;
            }
            case "fulfilled": {
                // This can occur when we already have a result written to the cache and
                // we subscribe for the first time. We create a fulfilled promise in the
                // constructor with a value that is the same as the first emitted value
                // so we want to skip delivering it.
                if ((0, equality_1.equal)(this.promise.value, result.data)) {
                    return;
                }
                this.promise =
                    result.complete ?
                        (0, utilities_1.createFulfilledPromise)(result.data)
                        : this.createPendingPromise();
                this.deliver(this.promise);
            }
        }
    }
    handleError(error) {
        this.reject?.(error);
    }
    deliver(promise) {
        this.listeners.forEach((listener) => listener(promise));
    }
    createPendingPromise() {
        return (0, utilities_1.wrapPromiseWithState)(new Promise((resolve, reject) => {
            this.resolve = resolve;
            this.reject = reject;
        }));
    }
    getDiff(client, options) {
        const { cache } = client;
        const { from, fragment, fragmentName } = options;
        const diff = cache.diff({
            ...options,
            query: cache["getFragmentDoc"](fragment, fragmentName),
            returnPartialData: true,
            id: from,
            optimistic: true,
        });
        return {
            ...diff,
            result: client["queryManager"].maskFragment({
                fragment,
                fragmentName,
                data: diff.result,
            }),
        };
    }
}
exports.FragmentReference = FragmentReference;
//# sourceMappingURL=FragmentReference.cjs.map
