"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstanceKeyLRUCache = exports.ElementLRUCache = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
/**
 * A LRU cache for entities. Cache contains the ElementProps and the load options used to load it.
 * Cache can be searched by id, code or federationGuid.
 */
class ElementLRUCache {
    capacity;
    static DEFAULT_CAPACITY = 2000;
    _elementCache = new Map();
    _cacheByCode = new Map();
    _cacheByFederationGuid = new Map();
    static makeCodeKey(code) {
        const keys = [code.scope, code.spec];
        if (code.value !== undefined) {
            keys.push(code.value);
        }
        return JSON.stringify(keys);
    }
    findElement(key) {
        if (key.id) {
            return this._elementCache.get(key.id);
        }
        else if (key.federationGuid) {
            const id = this._cacheByFederationGuid.get(key.federationGuid);
            if (id)
                return this._elementCache.get(id);
        }
        else if (key.code) {
            const id = this._cacheByCode.get(ElementLRUCache.makeCodeKey(key.code));
            if (id)
                return this._elementCache.get(id);
        }
        else {
            core_bentley_1.ITwinError.throwError({ message: "No key provided", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } });
        }
        return undefined;
    }
    constructor(capacity = ElementLRUCache.DEFAULT_CAPACITY) {
        this.capacity = capacity;
    }
    clear() {
        this._elementCache.clear();
        this._cacheByCode.clear();
        this._cacheByFederationGuid.clear();
    }
    get size() {
        return this._elementCache.size;
    }
    deleteWithModel(modelId) {
        this._elementCache.forEach((cachedVal, elementId) => {
            if (cachedVal.elProps.model === modelId)
                this.delete({ id: elementId });
        });
    }
    delete(key) {
        const cachedElement = this.findElement(key);
        if (cachedElement) {
            if (cachedElement.elProps.id)
                this._elementCache.delete(cachedElement.elProps.id);
            this._cacheByCode.delete(ElementLRUCache.makeCodeKey(cachedElement.elProps.code));
            if (cachedElement.elProps.federationGuid)
                this._cacheByFederationGuid.delete(cachedElement.elProps.federationGuid);
            return true;
        }
        return false;
    }
    get(key) {
        const cachedElement = this.findElement(key);
        if (cachedElement) {
            if (cachedElement.elProps.id) {
                this._elementCache.delete(cachedElement.elProps.id);
                this._elementCache.set(cachedElement.elProps.id, cachedElement);
            }
        }
        if (cachedElement) {
            if (key.displayStyle !== cachedElement.loadOptions.displayStyle ||
                key.onlyBaseProperties !== cachedElement.loadOptions.onlyBaseProperties ||
                key.renderTimeline !== cachedElement.loadOptions.renderTimeline ||
                key.wantBRepData !== cachedElement.loadOptions.wantBRepData ||
                key.wantGeometry !== cachedElement.loadOptions.wantGeometry) {
                return undefined;
            }
        }
        return cachedElement;
    }
    set(el) {
        if (!el.elProps.id)
            core_bentley_1.ITwinError.throwError({ message: "Element must have an id", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } });
        // do not cache this element as geom need to be rerender into geom props if any part changes
        // TODO: find a way to handle caching geometry
        if (el.loadOptions.wantGeometry) {
            return this;
        }
        if (this._elementCache.has(el.elProps.id)) {
            this._elementCache.delete(el.elProps.id);
            this._elementCache.set(el.elProps.id, el);
        }
        else {
            this._elementCache.set(el.elProps.id, el);
        }
        if (el.elProps.federationGuid) {
            this._cacheByFederationGuid.set(el.elProps.federationGuid, el.elProps.id);
        }
        this._cacheByCode.set(ElementLRUCache.makeCodeKey(el.elProps.code), el.elProps.id);
        if (this._elementCache.size > this.capacity) {
            const oldestKey = this._elementCache.keys().next().value;
            const oldestElement = this._elementCache.get(oldestKey);
            this.delete({ id: oldestKey, federationGuid: oldestElement?.elProps.federationGuid, code: oldestElement?.elProps.code });
        }
        return this;
    }
    get [Symbol.toStringTag]() {
        return `EntityCache(this.size=${this.size}, capacity=${this.capacity})`;
    }
}
exports.ElementLRUCache = ElementLRUCache;
;
/**
 * A map to store instance keys based on different optional arguments like id, code, or federationGuid.
 * This allows the cache to be searched by any combination of arguments.
 */
class ArgumentsToResultMap {
    _idToResult = new Map();
    _codeToResult = new Map();
    _federationGuidToResult = new Map();
    constructor() { }
    set(args, result) {
        if (!args.id && !args.code && !args.federationGuid)
            core_bentley_1.ITwinError.throwError({ message: "At least one id, code, or federationGuid must be provided", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } });
        if (args.id)
            this._idToResult.set(args.id, result);
        else
            this._idToResult.set(result.id, result);
        if (args.code)
            this._codeToResult.set(args.code, result);
        if (args.federationGuid)
            this._federationGuidToResult.set(args.federationGuid, result);
    }
    get(args) {
        if (!args.id && !args.code && !args.federationGuid)
            core_bentley_1.ITwinError.throwError({ message: "At least one id, code, or federationGuid must be provided", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } });
        if (args.id)
            return this._idToResult.get(args.id);
        else if (args.code)
            return this._codeToResult.get(args.code);
        else if (args.federationGuid)
            return this._federationGuidToResult.get(args.federationGuid);
        return undefined;
    }
    delete(args) {
        if (!args.id && !args.code && !args.federationGuid)
            core_bentley_1.ITwinError.throwError({ message: "At least one id, code, or federationGuid must be provided", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } });
        let deleted = false;
        if (args.id)
            deleted = this._idToResult.delete(args.id) ? true : deleted;
        if (args.code)
            deleted = this._codeToResult.delete(args.code) ? true : deleted;
        if (args.federationGuid)
            deleted = this._federationGuidToResult.delete(args.federationGuid) ? true : deleted;
        return deleted;
    }
    clear() {
        this._idToResult.clear();
        this._codeToResult.clear();
        this._federationGuidToResult.clear();
    }
    get size() {
        return this._idToResult.size; // Id to key will always be stored even if code or federationGuid are not provided.
    }
    get [Symbol.toStringTag]() {
        return `idCacheSize=${this._idToResult.size}, codeCacheSize=${this._codeToResult.size}, federationGuidCacheSize=${this._federationGuidToResult.size}`;
    }
}
/**
 * A LRU cache for entities. Cache contains the ElementProps and the load options used to load it.
 * Cache can be searched by id, code or federationGuid.
 */
class InstanceKeyLRUCache {
    capacity;
    static DEFAULT_CAPACITY = 2000;
    _argsToResultCache = new ArgumentsToResultMap();
    _resultToArgsCache = new Map();
    constructor(capacity = ElementLRUCache.DEFAULT_CAPACITY) {
        this.capacity = capacity;
    }
    set(key, result) {
        if (!result.id || !core_bentley_1.Id64.isValidId64(result.id))
            core_bentley_1.ITwinError.throwError({ message: "Invalid InstanceKey result", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } });
        const cacheArgs = InstanceKeyLRUCache.makeCachedArgs(key);
        cacheArgs.id = cacheArgs.id ? cacheArgs.id : result.id;
        const existingArgs = this._resultToArgsCache.get(result.id);
        if (existingArgs) {
            // Combine existing args with new args for more complete key
            this._argsToResultCache.delete(existingArgs);
            const combinedArgs = InstanceKeyLRUCache.combineCachedArgs(existingArgs, cacheArgs);
            this._argsToResultCache.set(combinedArgs, result);
            this._resultToArgsCache.set(result.id, combinedArgs);
        }
        else {
            this._argsToResultCache.set(cacheArgs, result);
            this._resultToArgsCache.set(result.id, cacheArgs);
        }
        if (this._resultToArgsCache.size > this.capacity) {
            const oldestKey = this._resultToArgsCache.keys().next().value;
            const oldestArgs = this._resultToArgsCache.get(oldestKey);
            this.deleteCachedArgs({ id: oldestArgs?.id, code: oldestArgs?.code, federationGuid: oldestArgs?.federationGuid });
        }
        return this;
    }
    get(key) {
        const args = InstanceKeyLRUCache.makeCachedArgs(key);
        const cachedResult = this._argsToResultCache.get(args);
        if (cachedResult) {
            // Pop the cached result to the end of the cache to mark it as recently used
            const cachedArgs = this._resultToArgsCache.get(cachedResult.id);
            if (cachedArgs) {
                this._resultToArgsCache.delete(cachedResult.id);
                this._resultToArgsCache.set(cachedResult.id, cachedArgs);
            }
        }
        return cachedResult;
    }
    deleteById(id) {
        if (!core_bentley_1.Id64.isValidId64(id))
            core_bentley_1.ITwinError.throwError({ message: "Invalid id provided", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } });
        const cacheArgs = {
            id: core_bentley_1.Id64.fromString(id),
        };
        return this.deleteCachedArgs(cacheArgs);
    }
    delete(key) {
        const cacheArgs = InstanceKeyLRUCache.makeCachedArgs(key);
        return this.deleteCachedArgs(cacheArgs);
    }
    deleteCachedArgs(key) {
        const result = this._argsToResultCache.get(key);
        if (result) {
            const argsToDelete = this._resultToArgsCache.get(result.id);
            this._argsToResultCache.delete({ id: argsToDelete?.id, code: argsToDelete?.code, federationGuid: argsToDelete?.federationGuid });
            this._resultToArgsCache.delete(result.id);
            return true;
        }
        return false;
    }
    static makeCachedArgs(args) {
        if (!args.partialKey && !args.code && !args.federationGuid)
            core_bentley_1.ITwinError.throwError({ message: "ResolveInstanceKeyArgs must have a partialKey, code, or federationGuid", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } });
        return {
            id: args.partialKey?.id,
            code: args.code ? InstanceKeyLRUCache.makeCodeKey(args.code) : undefined,
            federationGuid: args.federationGuid,
        };
    }
    static combineCachedArgs(originalArgs, newArgs) {
        if (!originalArgs.id && !originalArgs.code && !originalArgs.federationGuid && !newArgs.id && !newArgs.code && !newArgs.federationGuid)
            core_bentley_1.ITwinError.throwError({ message: "ResolveInstanceKeyArgs must have a partialKey, code, or federationGuid", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } });
        return {
            id: newArgs.id ? newArgs.id : originalArgs.id,
            code: newArgs.code ? newArgs.code : originalArgs.code,
            federationGuid: newArgs.federationGuid ? newArgs.federationGuid : originalArgs.federationGuid,
        };
    }
    static makeCodeKey(code) {
        const keys = [code.scope, code.spec];
        if (code.value !== undefined) {
            keys.push(code.value);
        }
        return JSON.stringify(keys);
    }
    clear() {
        this._argsToResultCache.clear();
        this._resultToArgsCache.clear();
    }
    get size() {
        return this._resultToArgsCache.size;
    }
    get [Symbol.toStringTag]() {
        return `InstanceKeyCache(size=${this.size}, capacity=${this.capacity})`;
    }
}
exports.InstanceKeyLRUCache = InstanceKeyLRUCache;
;
//# sourceMappingURL=ElementLRUCache.js.map