import { Observable } from "rxjs";
import { graphQLResultHasProtocolErrors, PROTOCOL_ERRORS_SYMBOL, } from "@apollo/client/errors";
import { ApolloLink } from "@apollo/client/link";
import { buildDelayFunction } from "./delayFunction.js";
import { buildRetryFunction } from "./retryFunction.js";
/**
 * Tracking and management of operations that may be (or currently are) retried.
 */
class RetryableOperation {
    observer;
    operation;
    forward;
    delayFor;
    retryIf;
    retryCount = 0;
    currentSubscription = null;
    timerId;
    constructor(observer, operation, forward, delayFor, retryIf) {
        this.observer = observer;
        this.operation = operation;
        this.forward = forward;
        this.delayFor = delayFor;
        this.retryIf = retryIf;
        this.try();
    }
    /**
     * Stop retrying for the operation, and cancel any in-progress requests.
     */
    cancel() {
        if (this.currentSubscription) {
            this.currentSubscription.unsubscribe();
        }
        clearTimeout(this.timerId);
        this.timerId = undefined;
        this.currentSubscription = null;
    }
    try() {
        this.currentSubscription = this.forward(this.operation).subscribe({
            next: (result) => {
                if (graphQLResultHasProtocolErrors(result)) {
                    this.onError(result.extensions[PROTOCOL_ERRORS_SYMBOL]);
                    // Unsubscribe from the current subscription to prevent the `complete`
                    // handler to be called as a result of the stream closing.
                    this.currentSubscription?.unsubscribe();
                    return;
                }
                this.observer.next(result);
            },
            error: this.onError,
            complete: this.observer.complete.bind(this.observer),
        });
    }
    onError = async (error) => {
        this.retryCount += 1;
        // Should we retry?
        const shouldRetry = await this.retryIf(this.retryCount, this.operation, error);
        if (shouldRetry) {
            this.scheduleRetry(this.delayFor(this.retryCount, this.operation, error));
            return;
        }
        this.observer.error(error);
    };
    scheduleRetry(delay) {
        if (this.timerId) {
            throw new Error(`RetryLink BUG! Encountered overlapping retries`);
        }
        this.timerId = setTimeout(() => {
            this.timerId = undefined;
            this.try();
        }, delay);
    }
}
export class RetryLink extends ApolloLink {
    delayFor;
    retryIf;
    constructor(options) {
        super();
        const { attempts, delay } = options || {};
        this.delayFor =
            typeof delay === "function" ? delay : buildDelayFunction(delay);
        this.retryIf =
            typeof attempts === "function" ? attempts : buildRetryFunction(attempts);
    }
    request(operation, nextLink) {
        return new Observable((observer) => {
            const retryable = new RetryableOperation(observer, operation, nextLink, this.delayFor, this.retryIf);
            return () => {
                retryable.cancel();
            };
        });
    }
}
//# sourceMappingURL=retryLink.js.map
