"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.formLinearFnWithAttemptOffset = exports.formLinearFn = exports.formExponentialFnWithAttemptOffset = exports.formExponentialFn = exports.Throttler = void 0;
/**
 * Used to give increasing delay times for throttling a single functionality.
 * Delay is based on previous attempts within specified time window, subtracting delay time.
 */
class Throttler {
    get numAttempts() {
        return this.startTimes.length;
    }
    /**
     * Gets all attempt start times after compensating for the delay times
     * by adding the delay times to the actual times.
     */
    getAttempts() {
        return [...this.startTimes];
    }
    /**
     * Latest attempt time after compensating for the delay time itself
     * by adding the delay time to the actual time.
     */
    get latestAttemptTime() {
        return this.startTimes.length > 0
            ? this.startTimes[this.startTimes.length - 1]
            : undefined;
    }
    constructor(
    /**
     * Width of sliding delay window in milliseconds.
     */
    delayWindowMs, 
    /**
     * Maximum delay allowed in milliseconds.
     */
    maxDelayMs, 
    /**
     * Delay function used to calculate what the delay should be.
     * The input is the number of attempts that occurred within the sliding window.
     * The result is the calculated delay in milliseconds.
     */
    delayFn) {
        this.delayWindowMs = delayWindowMs;
        this.maxDelayMs = maxDelayMs;
        this.delayFn = delayFn;
        this.startTimes = [];
    }
    getDelay() {
        const now = Date.now();
        const latestAttemptTime = this.latestAttemptTime;
        if (latestAttemptTime !== undefined) {
            // If getDelay was called sooner than the most recent delay,
            // subtract the remaining time, since we previously added it.
            const earlyMs = latestAttemptTime - now;
            if (earlyMs > 0) {
                this.startTimes = this.startTimes.map((t) => t - earlyMs);
            }
        }
        // Remove all attempts that have already fallen out of the window.
        this.startTimes = this.startTimes.filter((t) => now - t < this.delayWindowMs);
        // Compute delay, but do not exceed the specified max delay.
        const delayMs = Math.min(this.delayFn(this.startTimes.length), this.maxDelayMs);
        // Record this attempt start time.
        this.startTimes.push(now);
        // Account for the delay time, by effectively removing it from the delay window.
        this.startTimes = this.startTimes.map((t) => t + delayMs);
        if (delayMs === this.maxDelayMs) {
            // We hit max delay, so adding more won't affect anything.
            // Shift off oldest time to stop this array from growing forever.
            this.startTimes.shift();
        }
        return delayMs;
    }
}
exports.Throttler = Throttler;
/**
 * Helper function to generate simple exponential throttle functions.
 * f(n) = [coefficient] x ([multiplier]^n) + [flatOffset]
 * where n = number of attempts, and f(n) = delay time in milliseconds.
 * If not provided, coefficient will default to 1, multiplier to 2,
 * minimum delay to 0, and the offset to 0, yielding:
 * 0 ms, 2 ms, 4 ms, 8 ms, ..., 2^n ms
 * where M = multiplier; an exponential back-off.
 * Use initialDelay to decide what should happen when numAttempts is 0,
 * leave it undefined to not special case.
 */
const formExponentialFn = ({ multiplier = 2, coefficient = 1, offset = 0, initialDelay = undefined, } = {}) => (numAttempts) => Math.max(0, numAttempts <= 0 && initialDelay !== undefined
    ? initialDelay
    : coefficient * Math.pow(multiplier, numAttempts) + offset);
exports.formExponentialFn = formExponentialFn;
/**
 * f(n) = C x (B^(n+A)) + F = (C x B^A) x B^n + F
 */
const formExponentialFnWithAttemptOffset = (attemptOffset, { multiplier = 2, coefficient = 1, offset = 0, initialDelay = undefined, } = {}) => (0, exports.formExponentialFn)({
    multiplier,
    coefficient: coefficient * Math.pow(multiplier, attemptOffset),
    offset,
    initialDelay,
});
exports.formExponentialFnWithAttemptOffset = formExponentialFnWithAttemptOffset;
/**
 * Helper function to generate simple linear throttle functions.
 * f(n) = [coefficient] x n + [flatOffset]
 * where n = number of attempts, and f(n) = delay time in milliseconds.
 * If not provided, coefficient will default to 1, and offset to 0, yielding:
 * 0 ms, 1 ms, 2 ms, 3 ms, ..., n ms delays; a linear back-off.
 */
const formLinearFn = ({ coefficient = 1, offset = 0 } = {}) => (numAttempts) => Math.max(0, coefficient * numAttempts + offset);
exports.formLinearFn = formLinearFn;
/**
 * f(n) = C x (n+A) + F = C x n + (C x A + F)
 */
const formLinearFnWithAttemptOffset = (attemptOffset, { coefficient = 1, offset = 0 } = {}) => (0, exports.formLinearFn)({
    coefficient,
    offset: coefficient * attemptOffset + offset,
});
exports.formLinearFnWithAttemptOffset = formLinearFnWithAttemptOffset;
//# sourceMappingURL=throttler.js.map