var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
import { newContext } from "../context/context";
import { getLogger } from "../logger";
// const debug = Debug("node-ts-cache");
const DEFAULT_TTL_SECONDS = 60;
export class CacheContainer {
    constructor(storage) {
        this.storage = storage;
    }
    getItem(ctx, key) {
        return __awaiter(this, void 0, void 0, function* () {
            getLogger(ctx).with({ key: key }).debug("getting item");
            const item = yield this.storage.getItem(key);
            if (!item) {
                return undefined;
            }
            if (this.isItemExpired(item)) {
                yield this.cleanupItem(ctx, key);
                return undefined;
            }
            if (item.meta.borrowable) {
                item.meta.referencesCount++;
            }
            return item ? item.content : undefined;
        });
    }
    setItem(ctx, key, content, options) {
        return __awaiter(this, void 0, void 0, function* () {
            const finalOptions = Object.assign({ ttl: DEFAULT_TTL_SECONDS, isLazy: true, isCachedForever: false, borrow: false, borrowable: false, deletePostHook: undefined }, options);
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            const { deletePostHook: _ } = finalOptions, cleanOptions = __rest(finalOptions, ["deletePostHook"]);
            getLogger(ctx).with({ key: key, options: cleanOptions }).debug("setting");
            let meta = {
                expires: false,
                referencesCount: options.borrowable && options.borrow ? 1 : 0,
                borrowable: options.borrowable
            };
            if (!finalOptions.isCachedForever) {
                const ttlMilliseconds = finalOptions.ttl * 1000;
                meta = Object.assign(Object.assign({}, meta), { expires: true, ttl: ttlMilliseconds, createdAt: Date.now(), deletePostHook: finalOptions.deletePostHook });
                if (!finalOptions.isLazy) {
                    meta.timeout = setTimeout(() => {
                        this.cleanupItem(newContext(), key);
                    }, ttlMilliseconds);
                }
            }
            yield this.storage.setItem(key, { meta, content });
        });
    }
    removeItem(ctx, key, returnData = true, forcePostHook = false) {
        return __awaiter(this, void 0, void 0, function* () {
            getLogger(ctx).with({ key: key }).debug("removing");
            const item = yield this.storage.getItem(key);
            if (!item) {
                return undefined;
            }
            const expired = this.isItemExpired(item);
            // stop timeout event
            if (item.meta.timeout) {
                clearTimeout(item.meta.timeout);
            }
            if (expired && !forcePostHook) {
                yield this.storage.removeItem(key);
                return undefined;
            }
            if (item.meta.deletePostHook) {
                item.meta.deletePostHook(item.content);
            }
            yield this.storage.removeItem(key);
            if (!expired && returnData)
                return item.content;
            return undefined;
        });
    }
    release(ctx, key) {
        return __awaiter(this, void 0, void 0, function* () {
            getLogger(ctx).with({ key: key }).debug("releasing");
            const item = yield this.storage.getItem(key);
            if (!item) {
                return;
            }
            item.meta.referencesCount--;
            if (this.isItemExpired(item)) {
                this.removeItem(ctx, key);
            }
        });
    }
    clear() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.storage.clear();
        });
    }
    isItemExpired(item) {
        if (!item.meta.expires) {
            return false;
        }
        if (item.meta.referencesCount > 0) {
            return false;
        }
        return Date.now() > ((item.meta.createdAt || 0) + (item.meta.ttl || 0));
    }
    cleanupItem(ctx, key) {
        return __awaiter(this, void 0, void 0, function* () {
            getLogger(ctx).with({ key: key }).debug("cleaning up");
            const item = yield this.storage.getItem(key);
            if (!item) {
                return;
            }
            item.meta.timeout = undefined;
            if (item.meta.referencesCount > 0) {
                // TODO start final timout to cleanup deadlocked caches
                return;
            }
            this.removeItem(ctx, key, false, true);
        });
    }
}
//# sourceMappingURL=cache-container.js.map