"use strict";
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
Object.defineProperty(exports, "__esModule", { value: true });
exports.debounceAsync = debounceAsync;
exports.singleWorkerFromBatchWorker = singleWorkerFromBatchWorker;
// like lodash.debounce, but also avoids async invocations to overlap
function debounceAsync(
// the function you want to not call too often, too soon
callback, options = {}) {
    const { wait = 0, leading = false, maxWait = Infinity, cancelable = false, retain = 0, retainFailure, reuseRunning = Boolean(retain || retainFailure) } = options;
    let started = 0; // latest callback invocation
    let runningCallback; // latest callback invocation result
    let latestDebouncer; // latest wrapper invocation
    let waitingSince = 0; // we are delaying invocation since
    let whoIsWaiting; // args object identifies the pending instance, and incidentally stores args
    let latestCallback;
    let latestHasFailed = false;
    let latestTimestamp = 0;
    const interceptingWrapper = (...args) => latestDebouncer = debouncer(...args);
    return Object.assign(interceptingWrapper, {
        clearRetain: () => latestCallback = undefined,
        flush: () => runningCallback ?? exec(),
        isWorking: () => runningCallback,
        ...cancelable && {
            cancel() {
                waitingSince = 0;
                whoIsWaiting = undefined;
            }
        }
    });
    async function debouncer(...args) {
        if (reuseRunning && runningCallback)
            return runningCallback;
        const now = Date.now();
        if (latestCallback && now - latestTimestamp < (latestHasFailed ? retainFailure ?? retain : retain))
            return await latestCallback;
        whoIsWaiting = args;
        waitingSince ||= now;
        const waitingCap = maxWait - (now - (waitingSince || started));
        const waitFor = Math.min(waitingCap, leading ? wait - (now - started) : wait);
        if (waitFor > 0)
            await new Promise(resolve => setTimeout(resolve, waitFor));
        if (!whoIsWaiting) { // canceled
            waitingSince = 0;
            return undefined;
        }
        if (whoIsWaiting !== args) // another fresher call is waiting
            return latestDebouncer;
        await runningCallback; // in case we don't reuseRunning
        return exec();
    }
    async function exec() {
        if (!whoIsWaiting)
            return undefined;
        waitingSince = 0;
        started = Date.now();
        try {
            const args = whoIsWaiting;
            whoIsWaiting = undefined;
            runningCallback = Promise.resolve(callback(...args)); // cast to promise, in case callback was not really async (or hybrid)
            runningCallback.then(() => latestHasFailed = false, () => latestHasFailed = true);
            return await runningCallback; // await necessary to go-finally at the right time and even on exceptions
        }
        finally {
            latestCallback = runningCallback;
            latestTimestamp = Date.now();
            runningCallback = undefined;
        }
    }
}
// given a function that works on a batch of requests, returns the function that works on a single request
function singleWorkerFromBatchWorker(batchWorker, { maxWait = Infinity } = {}) {
    let batch = [];
    const debounced = debounceAsync(async () => {
        const ret = batchWorker(batch);
        batch = []; // this is reset as batchWorker starts, but without waiting
        return ret;
    }, { wait: 100, maxWait });
    return (...args) => {
        const idx = batch.push(args) - 1;
        return debounced().then((x) => x[idx]);
    };
}
