// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { BaseRequestPolicy, } from "./requestPolicy";
import { AbortError } from "@azure/abort-controller";
import { Constants } from "../util/constants";
import { DEFAULT_CLIENT_MAX_RETRY_COUNT } from "../util/throttlingRetryStrategy";
import { delay } from "../util/delay";
const StatusCodes = Constants.HttpConstants.StatusCodes;
/**
 * Creates a policy that re-sends the request if the response indicates the request failed because of throttling reasons.
 * For example, if the response contains a `Retry-After` header, it will retry sending the request based on the value of that header.
 *
 * To learn more, please refer to
 * https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-request-limits,
 * https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits and
 * https://docs.microsoft.com/en-us/azure/virtual-machines/troubleshooting/troubleshooting-throttling-errors
 * @returns
 */
export function throttlingRetryPolicy() {
    return {
        create: (nextPolicy, options) => {
            return new ThrottlingRetryPolicy(nextPolicy, options);
        },
    };
}
const StandardAbortMessage = "The operation was aborted.";
/**
 * Creates a policy that re-sends the request if the response indicates the request failed because of throttling reasons.
 * For example, if the response contains a `Retry-After` header, it will retry sending the request based on the value of that header.
 *
 * To learn more, please refer to
 * https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-request-limits,
 * https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits and
 * https://docs.microsoft.com/en-us/azure/virtual-machines/troubleshooting/troubleshooting-throttling-errors
 */
export class ThrottlingRetryPolicy extends BaseRequestPolicy {
    constructor(nextPolicy, options, _handleResponse) {
        super(nextPolicy, options);
        this.numberOfRetries = 0;
        this._handleResponse = _handleResponse || this._defaultResponseHandler;
    }
    async sendRequest(httpRequest) {
        const response = await this._nextPolicy.sendRequest(httpRequest.clone());
        if (response.status !== StatusCodes.TooManyRequests &&
            response.status !== StatusCodes.ServiceUnavailable) {
            return response;
        }
        else {
            return this._handleResponse(httpRequest, response);
        }
    }
    async _defaultResponseHandler(httpRequest, httpResponse) {
        var _a;
        const retryAfterHeader = httpResponse.headers.get(Constants.HeaderConstants.RETRY_AFTER);
        if (retryAfterHeader) {
            const delayInMs = ThrottlingRetryPolicy.parseRetryAfterHeader(retryAfterHeader);
            if (delayInMs) {
                this.numberOfRetries += 1;
                await delay(delayInMs, undefined, {
                    abortSignal: httpRequest.abortSignal,
                    abortErrorMsg: StandardAbortMessage,
                });
                if ((_a = httpRequest.abortSignal) === null || _a === void 0 ? void 0 : _a.aborted) {
                    throw new AbortError(StandardAbortMessage);
                }
                if (this.numberOfRetries < DEFAULT_CLIENT_MAX_RETRY_COUNT) {
                    return this.sendRequest(httpRequest);
                }
                else {
                    return this._nextPolicy.sendRequest(httpRequest);
                }
            }
        }
        return httpResponse;
    }
    static parseRetryAfterHeader(headerValue) {
        const retryAfterInSeconds = Number(headerValue);
        if (Number.isNaN(retryAfterInSeconds)) {
            return ThrottlingRetryPolicy.parseDateRetryAfterHeader(headerValue);
        }
        else {
            return retryAfterInSeconds * 1000;
        }
    }
    static parseDateRetryAfterHeader(headerValue) {
        try {
            const now = Date.now();
            const date = Date.parse(headerValue);
            const diff = date - now;
            return Number.isNaN(diff) ? undefined : diff;
        }
        catch (error) {
            return undefined;
        }
    }
}
//# sourceMappingURL=throttlingRetryPolicy.js.map