"use strict";
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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deprecated = exports.performanceLog = exports.exported = exports.invokeInit = exports.invokeBefore = exports.memoize = exports.cache = void 0;
const constants_1 = require("../constants");
const yok_1 = require("./yok");
function cache() {
    return (target, propertyKey, descriptor) => {
        let result;
        const propName = descriptor.value ? "value" : "get";
        const originalValue = descriptor[propName];
        descriptor[propName] = function (...args) {
            const propertyName = `__isCalled_${propertyKey}__`;
            if (this && !this[propertyName]) {
                this[propertyName] = true;
                result = originalValue.apply(this, args);
            }
            return result;
        };
        return descriptor;
    };
}
exports.cache = cache;
let memoizeIDCounter = 0;
function memoize(options) {
    return (target, propertyKey, descriptor) => {
        const DEBUG = false;
        const memoizeID = memoizeIDCounter++;
        const valueOrGet = descriptor.value ? "value" : "get";
        const originalMethod = descriptor[valueOrGet];
        descriptor[valueOrGet] = function (...args) {
            const cacheMapName = `__memoize_cache_map_${memoizeID}`;
            DEBUG && console.log(options);
            let hashKey;
            if (options.hashFn) {
                DEBUG && console.log({ args });
                hashKey = options.hashFn.apply(this, args);
            }
            else {
                hashKey = `__memoize_cache_value_${memoizeID}`;
            }
            DEBUG &&
                console.log({
                    cacheMapName,
                    hashKey,
                });
            if (!this.hasOwnProperty(cacheMapName)) {
                DEBUG && console.log("NO CACHE MAP YET, CREATING ONE NOW");
                Object.defineProperty(this, cacheMapName, {
                    configurable: false,
                    enumerable: false,
                    writable: false,
                    value: new Map(),
                });
            }
            const cacheMap = this[cacheMapName];
            DEBUG &&
                console.log({
                    cacheMap,
                });
            if (cacheMap.has(hashKey)) {
                DEBUG && console.log("CACHE HIT");
                return cacheMap.get(hashKey);
            }
            DEBUG && console.log("CACHE MISS");
            const result = originalMethod.apply(this, args);
            let shouldCache = true;
            if (options.shouldCache) {
                shouldCache = options.shouldCache.call(this, result);
            }
            DEBUG && console.log("GOT BACK SHOULDCACHE", shouldCache);
            if (shouldCache) {
                DEBUG && console.log("CACHING NOW");
                cacheMap.set(hashKey, result);
            }
            DEBUG && console.log("RETURNING", result);
            return result;
        };
        return descriptor;
    };
}
exports.memoize = memoize;
function invokeBefore(methodName, methodArgs) {
    return (target, propertyKey, descriptor) => {
        const originalValue = descriptor.value;
        descriptor.value = function (...args) {
            return __awaiter(this, void 0, void 0, function* () {
                yield target[methodName].apply(this, methodArgs);
                return originalValue.apply(this, args);
            });
        };
        return descriptor;
    };
}
exports.invokeBefore = invokeBefore;
function invokeInit() {
    return invokeBefore("init");
}
exports.invokeInit = invokeInit;
function exported(moduleName) {
    return (target, propertyKey, descriptor) => {
        yok_1.injector.publicApi.__modules__[moduleName] =
            yok_1.injector.publicApi.__modules__[moduleName] || {};
        yok_1.injector.publicApi.__modules__[moduleName][propertyKey] = (...args) => {
            const originalModule = yok_1.injector.resolve(moduleName), originalMethod = originalModule[propertyKey], result = originalMethod.apply(originalModule, args);
            return result;
        };
        return descriptor;
    };
}
exports.exported = exported;
function performanceLog(localInjector) {
    localInjector = localInjector || yok_1.injector;
    return function (target, propertyKey, descriptor) {
        const originalMethod = descriptor.value;
        const className = target.constructor.name;
        const trackName = `${className}${constants_1.AnalyticsEventLabelDelimiter}${propertyKey}`;
        const performanceService = localInjector.resolve("performanceService");
        const functionWrapper = {
            [originalMethod.name]: function (...args) {
                const start = performanceService.now();
                const result = originalMethod.apply(this, args);
                const resolvedPromise = Promise.resolve(result);
                let end;
                if (resolvedPromise !== result) {
                    end = performanceService.now();
                    performanceService.processExecutionData(trackName, start, end, args);
                }
                else {
                    resolvedPromise
                        .then(() => {
                        end = performanceService.now();
                        performanceService.processExecutionData(trackName, start, end, args);
                    })
                        .catch((err) => {
                        end = performanceService.now();
                        performanceService.processExecutionData(trackName, start, end, args);
                    });
                }
                return result;
            },
        };
        descriptor.value = functionWrapper[originalMethod.name];
        descriptor.value.toString = () => {
            return originalMethod.toString();
        };
        return descriptor;
    };
}
exports.performanceLog = performanceLog;
function deprecated(additionalInfo, localInjector) {
    const isDeprecatedMessage = " is deprecated.";
    return (target, key, descriptor) => {
        localInjector = localInjector || yok_1.injector;
        additionalInfo = additionalInfo || "";
        const $logger = localInjector.resolve("logger");
        if (descriptor) {
            if (descriptor.value) {
                const originalMethod = descriptor.value;
                descriptor.value = function (...args) {
                    $logger.warn(`${key.toString()}${isDeprecatedMessage} ${additionalInfo}`);
                    return originalMethod.apply(this, args);
                };
                return descriptor;
            }
            else {
                if (descriptor.set) {
                    const originalSetter = descriptor.set;
                    descriptor.set = function (...args) {
                        $logger.warn(`${key.toString()}${isDeprecatedMessage} ${additionalInfo}`);
                        originalSetter.apply(this, args);
                    };
                }
                if (descriptor.get) {
                    const originalGetter = descriptor.get;
                    descriptor.get = function (...args) {
                        $logger.warn(`${key.toString()}${isDeprecatedMessage} ${additionalInfo}`);
                        return originalGetter.apply(this, args);
                    };
                }
                return descriptor;
            }
        }
        else {
            $logger.warn(`${(target &&
                (target.name ||
                    (target.constructor && target.constructor.name))) ||
                target}${isDeprecatedMessage} ${additionalInfo}`);
            return target;
        }
    };
}
exports.deprecated = deprecated;
