/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Logger } from "@itwin/core-bentley";
import { BackendLoggerCategory } from "./BackendLoggerCategory";
/** Wrapper around a promise that allows synchronous queries of it's state
 * @internal
 */
export class QueryablePromise {
    promise;
    result;
    error;
    _fulfilled = false;
    _rejected = false;
    get isPending() { return !this.isFulfilled && !this.isRejected; }
    get isFulfilled() { return this._fulfilled; }
    get isRejected() { return this._rejected; }
    constructor(promise) {
        this.promise = promise;
        this.promise.then((res) => {
            this.result = res;
            this._fulfilled = true;
        }).catch((err) => {
            this.error = err;
            this._rejected = true;
        });
    }
}
/** Utility to cache and retrieve results of long running asynchronous functions.
 * The cache is keyed on the input arguments passed to these functions
 * @internal
 */
export class PromiseMemoizer {
    _cachedPromises = new Map();
    _timers = new Map();
    _memoizeFn;
    _generateKeyFn;
    _maxCacheSize;
    _cacheTimeout;
    /**
     * Constructor
     * @param memoizeFn Function to memoize
     * @param generateKeyFn Function to generate the key for the memoized function
     * @param maxCacheSize Maximum size of the memoizer cache.
     * If the maximum cache size is exceeded, fulfilled/rejected entries are first discarded - these
     * may have been unclaimed/orphaned promises. If the cache size is still above the maxCacheSize
     * threshold, the entire cache is then cleared.
     */
    constructor(memoizeFn, generateKeyFn, maxCacheSize = 500, cacheTimeout = 30000) {
        this._memoizeFn = memoizeFn;
        this._generateKeyFn = generateKeyFn;
        this._maxCacheSize = maxCacheSize;
        this._cacheTimeout = cacheTimeout;
    }
    /** Call the memoized function */
    memoize(...args) {
        const key = this._generateKeyFn(...args);
        let qp = this._cachedPromises.get(key);
        if (qp)
            return qp;
        if (this._cachedPromises.size >= this._maxCacheSize) {
            if (this._maxCacheSize > 1)
                Logger.logError(BackendLoggerCategory.PromiseMemoizer, "Cleared too many unresolved entries in memoizer cache");
            this.clearCache();
        }
        const removeCachedPromise = (v) => {
            const cleanUp = () => {
                this._cachedPromises.delete(key);
                this._timers.delete(key);
            };
            this._timers.set(key, setTimeout(cleanUp, this._cacheTimeout));
            return v;
        };
        const p = this._memoizeFn(...args).then(removeCachedPromise, (e) => {
            throw removeCachedPromise(e); // eslint-disable-line @typescript-eslint/only-throw-error
        });
        qp = new QueryablePromise(p);
        this._cachedPromises.set(key, qp);
        return qp;
    }
    /** Delete the memoized function */
    deleteMemoized(...args) {
        const key = this._generateKeyFn(...args);
        this._cachedPromises.delete(key);
        const timer = this._timers.get(key);
        if (timer)
            clearTimeout(timer);
    }
    /** Clear all entries in the memoizer cache */
    clearCache() {
        this._cachedPromises.clear();
    }
    [Symbol.dispose]() {
        for (const timer of this._timers.values())
            clearTimeout(timer);
        this._timers.clear();
        this.clearCache();
    }
}
//# sourceMappingURL=PromiseMemoizer.js.map