"use strict";
/**
 * Creates a debounced function that delays invoking func until after wait
 * milliseconds have elapsed since the last time the debounced function was
 * invoked. The debounced function comes with a cancel method to cancel
 * delayed func invocations and a flush method to immediately invoke them.
 *
 * Supports options: { leading, trailing, maxWait }
 * Drop-in replacement for lodash/debounce.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = debounce;
function debounce(func, wait = 0, options) {
    let lastArgs;
    let lastThis;
    let result;
    let timerId;
    let lastCallTime;
    let lastInvokeTime = 0;
    const leading = options?.leading ?? false;
    const trailing = options?.trailing ?? true;
    const maxWait = options?.maxWait;
    const hasMaxWait = maxWait !== undefined;
    const maxing = hasMaxWait;
    function invokeFunc(time) {
        const args = lastArgs;
        const thisArg = lastThis;
        lastArgs = undefined;
        lastThis = undefined;
        lastInvokeTime = time;
        result = func.apply(thisArg, args);
        return result;
    }
    function startTimer(pendingFunc, waitMs) {
        timerId = setTimeout(pendingFunc, waitMs);
    }
    function cancelTimer() {
        if (timerId !== undefined) {
            clearTimeout(timerId);
        }
        timerId = undefined;
    }
    function remainingWait(time) {
        const timeSinceLastCall = time - (lastCallTime ?? 0);
        const timeSinceLastInvoke = time - lastInvokeTime;
        const timeWaiting = wait - timeSinceLastCall;
        return maxing
            ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
            : timeWaiting;
    }
    function shouldInvoke(time) {
        const timeSinceLastCall = time - (lastCallTime ?? 0);
        const timeSinceLastInvoke = time - lastInvokeTime;
        return (lastCallTime === undefined ||
            timeSinceLastCall >= wait ||
            timeSinceLastCall < 0 ||
            (maxing && timeSinceLastInvoke >= maxWait));
    }
    function timerExpired() {
        const time = Date.now();
        if (shouldInvoke(time)) {
            trailingEdge(time);
            return;
        }
        // Restart the timer
        startTimer(timerExpired, remainingWait(time));
    }
    function trailingEdge(time) {
        timerId = undefined;
        if (trailing && lastArgs) {
            invokeFunc(time);
        }
        else {
            lastArgs = undefined;
            lastThis = undefined;
        }
    }
    function leadingEdge(time) {
        lastInvokeTime = time;
        startTimer(timerExpired, wait);
        if (leading) {
            invokeFunc(time);
        }
    }
    function debounced(...args) {
        const time = Date.now();
        const isInvoking = shouldInvoke(time);
        lastArgs = args;
        lastThis = this;
        lastCallTime = time;
        if (isInvoking) {
            if (timerId === undefined) {
                leadingEdge(time);
                return result;
            }
            if (maxing) {
                // Handle invocations in a tight loop with maxWait
                cancelTimer();
                startTimer(timerExpired, wait);
                invokeFunc(time);
                return result;
            }
        }
        if (timerId === undefined) {
            startTimer(timerExpired, wait);
        }
        return result;
    }
    debounced.cancel = function cancel() {
        cancelTimer();
        lastInvokeTime = 0;
        lastArgs = undefined;
        lastCallTime = undefined;
        lastThis = undefined;
    };
    debounced.flush = function flush() {
        if (timerId === undefined) {
            return result;
        }
        trailingEdge(Date.now());
        return result;
    };
    return debounced;
}
