import BottleneckLight from 'bottleneck/light';

const VERSION = "5.2.3";

const noop = () => Promise.resolve();
// @ts-expect-error
function wrapRequest(state, request, options) {
    return state.retryLimiter.schedule(doRequest, state, request, options);
}
// @ts-expect-error
async function doRequest(state, request, options) {
    const isWrite = options.method !== "GET" && options.method !== "HEAD";
    const { pathname } = new URL(options.url, "http://github.test");
    const isSearch = options.method === "GET" && pathname.startsWith("/search/");
    const isGraphQL = pathname.startsWith("/graphql");
    const retryCount = ~~request.retryCount;
    const jobOptions = retryCount > 0 ? { priority: 0, weight: 0 } : {};
    if (state.clustering) {
        // Remove a job from Redis if it has not completed or failed within 60s
        // Examples: Node process terminated, client disconnected, etc.
        // @ts-expect-error
        jobOptions.expiration = 1000 * 60;
    }
    // Guarantee at least 1000ms between writes
    // GraphQL can also trigger writes
    if (isWrite || isGraphQL) {
        await state.write.key(state.id).schedule(jobOptions, noop);
    }
    // Guarantee at least 3000ms between requests that trigger notifications
    if (isWrite && state.triggersNotification(pathname)) {
        await state.notifications.key(state.id).schedule(jobOptions, noop);
    }
    // Guarantee at least 2000ms between search requests
    if (isSearch) {
        await state.search.key(state.id).schedule(jobOptions, noop);
    }
    const req = state.global.key(state.id).schedule(jobOptions, request, options);
    if (isGraphQL) {
        const res = await req;
        if (res.data.errors != null &&
            // @ts-expect-error
            res.data.errors.some((error) => error.type === "RATE_LIMITED")) {
            const error = Object.assign(new Error("GraphQL Rate Limit Exceeded"), {
                response: res,
                data: res.data,
            });
            throw error;
        }
    }
    return req;
}

var triggersNotificationPaths = [
    "/orgs/{org}/invitations",
    "/orgs/{org}/invitations/{invitation_id}",
    "/orgs/{org}/teams/{team_slug}/discussions",
    "/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments",
    "/repos/{owner}/{repo}/collaborators/{username}",
    "/repos/{owner}/{repo}/commits/{commit_sha}/comments",
    "/repos/{owner}/{repo}/issues",
    "/repos/{owner}/{repo}/issues/{issue_number}/comments",
    "/repos/{owner}/{repo}/pulls",
    "/repos/{owner}/{repo}/pulls/{pull_number}/comments",
    "/repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies",
    "/repos/{owner}/{repo}/pulls/{pull_number}/merge",
    "/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers",
    "/repos/{owner}/{repo}/pulls/{pull_number}/reviews",
    "/repos/{owner}/{repo}/releases",
    "/teams/{team_id}/discussions",
    "/teams/{team_id}/discussions/{discussion_number}/comments",
];

function routeMatcher(paths) {
    // EXAMPLE. For the following paths:
    /* [
        "/orgs/{org}/invitations",
        "/repos/{owner}/{repo}/collaborators/{username}"
    ] */
    const regexes = paths.map((path) => path
        .split("/")
        .map((c) => (c.startsWith("{") ? "(?:.+?)" : c))
        .join("/"));
    // 'regexes' would contain:
    /* [
        '/orgs/(?:.+?)/invitations',
        '/repos/(?:.+?)/(?:.+?)/collaborators/(?:.+?)'
    ] */
    const regex = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
    // 'regex' would contain:
    /*
      ^(?:(?:\/orgs\/(?:.+?)\/invitations)|(?:\/repos\/(?:.+?)\/(?:.+?)\/collaborators\/(?:.+?)))[^\/]*$
  
      It may look scary, but paste it into https://www.debuggex.com/
      and it will make a lot more sense!
    */
    return new RegExp(regex, "i");
}

// @ts-expect-error
// Workaround to allow tests to directly access the triggersNotification function.
const regex = routeMatcher(triggersNotificationPaths);
const triggersNotification = regex.test.bind(regex);
const groups = {};
// @ts-expect-error
const createGroups = function (Bottleneck, common) {
    groups.global = new Bottleneck.Group({
        id: "octokit-global",
        maxConcurrent: 10,
        ...common,
    });
    groups.search = new Bottleneck.Group({
        id: "octokit-search",
        maxConcurrent: 1,
        minTime: 2000,
        ...common,
    });
    groups.write = new Bottleneck.Group({
        id: "octokit-write",
        maxConcurrent: 1,
        minTime: 1000,
        ...common,
    });
    groups.notifications = new Bottleneck.Group({
        id: "octokit-notifications",
        maxConcurrent: 1,
        minTime: 3000,
        ...common,
    });
};
function throttling(octokit, octokitOptions) {
    const { enabled = true, Bottleneck = BottleneckLight, id = "no-id", timeout = 1000 * 60 * 2, // Redis TTL: 2 minutes
    connection, } = octokitOptions.throttle || {};
    if (!enabled) {
        return {};
    }
    const common = { connection, timeout };
    if (groups.global == null) {
        createGroups(Bottleneck, common);
    }
    if (octokitOptions.throttle &&
        octokitOptions.throttle.minimalSecondaryRateRetryAfter) {
        octokit.log.warn("[@octokit/plugin-throttling] `options.throttle.minimalSecondaryRateRetryAfter` is deprecated, please use `options.throttle.fallbackSecondaryRateRetryAfter` instead");
        octokitOptions.throttle.fallbackSecondaryRateRetryAfter =
            octokitOptions.throttle.minimalSecondaryRateRetryAfter;
        delete octokitOptions.throttle.minimalSecondaryRateRetryAfter;
    }
    if (octokitOptions.throttle && octokitOptions.throttle.onAbuseLimit) {
        octokit.log.warn("[@octokit/plugin-throttling] `onAbuseLimit()` is deprecated and will be removed in a future release of `@octokit/plugin-throttling`, please use the `onSecondaryRateLimit` handler instead");
        // @ts-ignore types don't allow for both properties to be set
        octokitOptions.throttle.onSecondaryRateLimit =
            octokitOptions.throttle.onAbuseLimit;
        // @ts-ignore
        delete octokitOptions.throttle.onAbuseLimit;
    }
    const state = Object.assign({
        clustering: connection != null,
        triggersNotification,
        fallbackSecondaryRateRetryAfter: 60,
        retryAfterBaseValue: 1000,
        retryLimiter: new Bottleneck(),
        id,
        ...groups,
    }, octokitOptions.throttle);
    if (typeof state.onSecondaryRateLimit !== "function" ||
        typeof state.onRateLimit !== "function") {
        throw new Error(`octokit/plugin-throttling error:
        You must pass the onSecondaryRateLimit and onRateLimit error handlers.
        See https://octokit.github.io/rest.js/#throttling

        const octokit = new Octokit({
          throttle: {
            onSecondaryRateLimit: (retryAfter, options) => {/* ... */},
            onRateLimit: (retryAfter, options) => {/* ... */}
          }
        })
    `);
    }
    const events = {};
    const emitter = new Bottleneck.Events(events);
    // @ts-expect-error
    events.on("secondary-limit", state.onSecondaryRateLimit);
    // @ts-expect-error
    events.on("rate-limit", state.onRateLimit);
    // @ts-expect-error
    events.on("error", (e) => octokit.log.warn("Error in throttling-plugin limit handler", e));
    // @ts-expect-error
    state.retryLimiter.on("failed", async function (error, info) {
        const [state, request, options] = info.args;
        const { pathname } = new URL(options.url, "http://github.test");
        const shouldRetryGraphQL = pathname.startsWith("/graphql") && error.status !== 401;
        if (!(shouldRetryGraphQL || error.status === 403)) {
            return;
        }
        const retryCount = ~~request.retryCount;
        request.retryCount = retryCount;
        // backward compatibility
        options.request.retryCount = retryCount;
        const { wantRetry, retryAfter = 0 } = await (async function () {
            if (/\bsecondary rate\b/i.test(error.message)) {
                // The user has hit the secondary rate limit. (REST and GraphQL)
                // https://docs.github.com/en/rest/overview/resources-in-the-rest-api#secondary-rate-limits
                // The Retry-After header can sometimes be blank when hitting a secondary rate limit,
                // but is always present after 2-3s, so make sure to set `retryAfter` to at least 5s by default.
                const retryAfter = Number(error.response.headers["retry-after"]) ||
                    state.fallbackSecondaryRateRetryAfter;
                const wantRetry = await emitter.trigger("secondary-limit", retryAfter, options, octokit, retryCount);
                return { wantRetry, retryAfter };
            }
            if (error.response.headers != null &&
                error.response.headers["x-ratelimit-remaining"] === "0") {
                // The user has used all their allowed calls for the current time period (REST and GraphQL)
                // https://docs.github.com/en/rest/reference/rate-limit (REST)
                // https://docs.github.com/en/graphql/overview/resource-limitations#rate-limit (GraphQL)
                const rateLimitReset = new Date(~~error.response.headers["x-ratelimit-reset"] * 1000).getTime();
                const retryAfter = Math.max(Math.ceil((rateLimitReset - Date.now()) / 1000), 0);
                const wantRetry = await emitter.trigger("rate-limit", retryAfter, options, octokit, retryCount);
                return { wantRetry, retryAfter };
            }
            return {};
        })();
        if (wantRetry) {
            request.retryCount++;
            return retryAfter * state.retryAfterBaseValue;
        }
    });
    octokit.hook.wrap("request", wrapRequest.bind(null, state));
    return {};
}
throttling.VERSION = VERSION;
throttling.triggersNotification = triggersNotification;

export { throttling };
//# sourceMappingURL=index.js.map
