import { _ as _define_property } from "@swc/helpers/_/_define_property";
import { getWindow } from './getWindow';
/**
 * Bugs often appear in async code when stuff gets disposed, but async operations don't get canceled.
 * This Async helper class solves these issues by tying async code to the lifetime of a disposable object.
 *
 * Usage: Anything class extending from BaseModel can access this helper via this.async. Otherwise create a
 * new instance of the class and remember to call dispose() during your code's dispose handler.
 *
 * @public
 */ export class Async {
    /**
   * Dispose function, clears all async operations.
   */ dispose() {
        let id;
        this._isDisposed = true;
        this._parent = null;
        // Clear timeouts.
        if (this._timeoutIds) {
            for(id in this._timeoutIds){
                if (this._timeoutIds.hasOwnProperty(id)) {
                    this.clearTimeout(parseInt(id, 10));
                }
            }
            this._timeoutIds = null;
        }
        // Clear immediates.
        if (this._immediateIds) {
            for(id in this._immediateIds){
                if (this._immediateIds.hasOwnProperty(id)) {
                    this.clearImmediate(parseInt(id, 10));
                }
            }
            this._immediateIds = null;
        }
        // Clear intervals.
        if (this._intervalIds) {
            for(id in this._intervalIds){
                if (this._intervalIds.hasOwnProperty(id)) {
                    this.clearInterval(parseInt(id, 10));
                }
            }
            this._intervalIds = null;
        }
        // Clear animation frames.
        if (this._animationFrameIds) {
            for(id in this._animationFrameIds){
                if (this._animationFrameIds.hasOwnProperty(id)) {
                    this.cancelAnimationFrame(parseInt(id, 10));
                }
            }
            this._animationFrameIds = null;
        }
    }
    /**
   * SetTimeout override, which will auto cancel the timeout during dispose.
   * @param callback - Callback to execute.
   * @param duration - Duration in milliseconds.
   * @returns The setTimeout id.
   */ setTimeout(callback, duration) {
        let timeoutId = 0;
        if (!this._isDisposed) {
            if (!this._timeoutIds) {
                this._timeoutIds = {};
            }
            timeoutId = setTimeout(()=>{
                // Time to execute the timeout, enqueue it as a foreground task to be executed.
                try {
                    // Now delete the record and call the callback.
                    if (this._timeoutIds) {
                        delete this._timeoutIds[timeoutId];
                    }
                    callback.apply(this._parent);
                } catch (e) {
                    this._logError(e);
                }
            }, duration);
            this._timeoutIds[timeoutId] = true;
        }
        return timeoutId;
    }
    /**
   * Clears the timeout.
   * @param id - Id to cancel.
   */ clearTimeout(id) {
        if (this._timeoutIds && this._timeoutIds[id]) {
            clearTimeout(id);
            delete this._timeoutIds[id];
        }
    }
    /**
   * SetImmediate override, which will auto cancel the immediate during dispose.
   * @param callback - Callback to execute.
   * @param targetElement - Optional target element to use for identifying the correct window.
   * @returns The setTimeout id.
   */ setImmediate(callback, targetElement) {
        let immediateId = 0;
        const win = getWindow(targetElement);
        if (!this._isDisposed) {
            if (!this._immediateIds) {
                this._immediateIds = {};
            }
            const setImmediateCallback = ()=>{
                // Time to execute the timeout, enqueue it as a foreground task to be executed.
                try {
                    // Now delete the record and call the callback.
                    if (this._immediateIds) {
                        delete this._immediateIds[immediateId];
                    }
                    callback.apply(this._parent);
                } catch (e) {
                    this._logError(e);
                }
            };
            immediateId = win.setTimeout(setImmediateCallback, 0);
            this._immediateIds[immediateId] = true;
        }
        return immediateId;
    }
    /**
   * Clears the immediate.
   * @param id - Id to cancel.
   * @param targetElement - Optional target element to use for identifying the correct window.
   */ clearImmediate(id, targetElement) {
        const win = getWindow(targetElement);
        if (this._immediateIds && this._immediateIds[id]) {
            win.clearTimeout(id);
            delete this._immediateIds[id];
        }
    }
    /**
   * SetInterval override, which will auto cancel the timeout during dispose.
   * @param callback - Callback to execute.
   * @param duration - Duration in milliseconds.
   * @returns The setTimeout id.
   */ setInterval(callback, duration) {
        let intervalId = 0;
        if (!this._isDisposed) {
            if (!this._intervalIds) {
                this._intervalIds = {};
            }
            intervalId = setInterval(()=>{
                // Time to execute the interval callback, enqueue it as a foreground task to be executed.
                try {
                    callback.apply(this._parent);
                } catch (e) {
                    this._logError(e);
                }
            }, duration);
            this._intervalIds[intervalId] = true;
        }
        return intervalId;
    }
    /**
   * Clears the interval.
   * @param id - Id to cancel.
   */ clearInterval(id) {
        if (this._intervalIds && this._intervalIds[id]) {
            clearInterval(id);
            delete this._intervalIds[id];
        }
    }
    /**
   * Creates a function that, when executed, will only call the func function at most once per
   * every wait milliseconds. Provide an options object to indicate that func should be invoked
   * on the leading and/or trailing edge of the wait timeout. Subsequent calls to the throttled
   * function will return the result of the last func call.
   *
   * Note: If leading and trailing options are true func will be called on the trailing edge of
   * the timeout only if the throttled function is invoked more than once during the wait timeout.
   *
   * @param func - The function to throttle.
   * @param wait - The number of milliseconds to throttle executions to. Defaults to 0.
   * @param options - The options object.
   * @returns The new throttled function.
   */ // eslint-disable-next-line @typescript-eslint/no-explicit-any
    throttle(func, wait, options) {
        if (this._isDisposed) {
            return this._noop;
        }
        const waitMS = wait || 0;
        let leading = true;
        let trailing = true;
        let lastExecuteTime = 0;
        let lastResult;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let lastArgs;
        let timeoutId = null;
        if (options && typeof options.leading === 'boolean') {
            leading = options.leading;
        }
        if (options && typeof options.trailing === 'boolean') {
            trailing = options.trailing;
        }
        const callback = (userCall)=>{
            const now = Date.now();
            const delta = now - lastExecuteTime;
            const waitLength = leading ? waitMS - delta : waitMS;
            if (delta >= waitMS && (!userCall || leading)) {
                lastExecuteTime = now;
                if (timeoutId) {
                    this.clearTimeout(timeoutId);
                    timeoutId = null;
                }
                lastResult = func.apply(this._parent, lastArgs);
            } else if (timeoutId === null && trailing) {
                timeoutId = this.setTimeout(callback, waitLength);
            }
            return lastResult;
        };
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const resultFunction = (...args)=>{
            lastArgs = args;
            return callback(true);
        };
        return resultFunction;
    }
    /**
   * Creates a function that will delay the execution of func until after wait milliseconds have
   * elapsed since the last time it was invoked. Provide an options object to indicate that func
   * should be invoked on the leading and/or trailing edge of the wait timeout. Subsequent calls
   * to the debounced function will return the result of the last func call.
   *
   * Note: If leading and trailing options are true func will be called on the trailing edge of
   * the timeout only if the debounced function is invoked more than once during the wait
   * timeout.
   *
   * @param func - The function to debounce.
   * @param wait - The number of milliseconds to delay.
   * @param options - The options object.
   * @returns The new debounced function.
   */ // eslint-disable-next-line @typescript-eslint/no-explicit-any
    debounce(func, wait, options) {
        if (this._isDisposed) {
            const noOpFunction = ()=>{
            /** Do nothing */ };
            noOpFunction.cancel = ()=>{
                return;
            };
            noOpFunction.flush = ()=>null;
            noOpFunction.pending = ()=>false;
            return noOpFunction;
        }
        const waitMS = wait || 0;
        let leading = false;
        let trailing = true;
        let maxWait = null;
        let lastCallTime = 0;
        let lastExecuteTime = Date.now();
        let lastResult;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let lastArgs;
        let timeoutId = null;
        if (options && typeof options.leading === 'boolean') {
            leading = options.leading;
        }
        if (options && typeof options.trailing === 'boolean') {
            trailing = options.trailing;
        }
        if (options && typeof options.maxWait === 'number' && !isNaN(options.maxWait)) {
            maxWait = options.maxWait;
        }
        const markExecuted = (time)=>{
            if (timeoutId) {
                this.clearTimeout(timeoutId);
                timeoutId = null;
            }
            lastExecuteTime = time;
        };
        const invokeFunction = (time)=>{
            markExecuted(time);
            lastResult = func.apply(this._parent, lastArgs);
        };
        const callback = (userCall)=>{
            const now = Date.now();
            let executeImmediately = false;
            if (userCall) {
                if (leading && now - lastCallTime >= waitMS) {
                    executeImmediately = true;
                }
                lastCallTime = now;
            }
            const delta = now - lastCallTime;
            let waitLength = waitMS - delta;
            const maxWaitDelta = now - lastExecuteTime;
            let maxWaitExpired = false;
            if (maxWait !== null) {
                // maxWait only matters when there is a pending callback
                if (maxWaitDelta >= maxWait && timeoutId) {
                    maxWaitExpired = true;
                } else {
                    waitLength = Math.min(waitLength, maxWait - maxWaitDelta);
                }
            }
            if (delta >= waitMS || maxWaitExpired || executeImmediately) {
                invokeFunction(now);
            } else if ((timeoutId === null || !userCall) && trailing) {
                timeoutId = this.setTimeout(callback, waitLength);
            }
            return lastResult;
        };
        const pending = ()=>{
            return !!timeoutId;
        };
        const cancel = ()=>{
            if (pending()) {
                // Mark the debounced function as having executed
                markExecuted(Date.now());
            }
        };
        const flush = ()=>{
            if (pending()) {
                invokeFunction(Date.now());
            }
            return lastResult;
        };
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const resultFunction = (...args)=>{
            lastArgs = args;
            return callback(true);
        };
        resultFunction.cancel = cancel;
        resultFunction.flush = flush;
        resultFunction.pending = pending;
        return resultFunction;
    }
    requestAnimationFrame(callback, targetElement) {
        let animationFrameId = 0;
        const win = getWindow(targetElement);
        if (!this._isDisposed) {
            if (!this._animationFrameIds) {
                this._animationFrameIds = {};
            }
            const animationFrameCallback = ()=>{
                try {
                    // Now delete the record and call the callback.
                    if (this._animationFrameIds) {
                        delete this._animationFrameIds[animationFrameId];
                    }
                    callback.apply(this._parent);
                } catch (e) {
                    this._logError(e);
                }
            };
            animationFrameId = win.requestAnimationFrame ? win.requestAnimationFrame(animationFrameCallback) : win.setTimeout(animationFrameCallback, 0);
            this._animationFrameIds[animationFrameId] = true;
        }
        return animationFrameId;
    }
    cancelAnimationFrame(id, targetElement) {
        const win = getWindow(targetElement);
        if (this._animationFrameIds && this._animationFrameIds[id]) {
            win.cancelAnimationFrame ? win.cancelAnimationFrame(id) : win.clearTimeout(id);
            delete this._animationFrameIds[id];
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    _logError(e) {
        if (this._onErrorHandler) {
            this._onErrorHandler(e);
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    constructor(parent, onError){
        _define_property(this, "_timeoutIds", null);
        _define_property(this, "_immediateIds", null);
        _define_property(this, "_intervalIds", null);
        _define_property(this, "_animationFrameIds", null);
        _define_property(this, "_isDisposed", void 0);
        _define_property(this, "_parent", void 0);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        _define_property(this, "_onErrorHandler", void 0);
        _define_property(this, "_noop", void 0);
        this._isDisposed = false;
        this._parent = parent || null;
        this._onErrorHandler = onError;
        this._noop = ()=>{
        /* do nothing */ };
    }
}
