"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments)).next());
    });
};
const assert = require("assert");
const helpers_1 = require("./helpers");
function cache() {
    return (target, propertyKey, descriptor) => {
        let result;
        let propName = descriptor.value ? "value" : "get";
        const originalValue = descriptor[propName];
        descriptor[propName] = function (...args) {
            let propertyName = `__isCalled_${propertyKey}__`;
            if (this && !this[propertyName]) {
                this[propertyName] = true;
                result = originalValue.apply(this, args);
            }
            return result;
        };
        return descriptor;
    };
}
exports.cache = cache;
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 exportedPromise(moduleName, postAction) {
    return (target, propertyKey, descriptor) => {
        $injector.publicApi.__modules__[moduleName] = $injector.publicApi.__modules__[moduleName] || {};
        $injector.publicApi.__modules__[moduleName][propertyKey] = (...args) => {
            let originalModule = $injector.resolve(moduleName);
            let originalMethod = originalModule[propertyKey];
            let result;
            try {
                result = originalMethod.apply(originalModule, args);
            }
            catch (err) {
                let promise = new Promise((onFulfilled, onRejected) => {
                    onRejected(err);
                });
                return promise;
            }
            let types = _(result)
                .groupBy((f) => typeof f)
                .keys()
                .value(), postActionMethod = postAction && postAction.bind(originalModule);
            if (_.isArray(result) && types.length === 1 && helpers_1.isPromise(_.first(result))) {
                return _.map(result, (future, index) => getPromise(future, {
                    postActionMethod,
                    shouldExecutePostAction: (index + 1) === result.length
                }));
            }
            else {
                return getPromise(result, {
                    postActionMethod,
                    shouldExecutePostAction: !!postAction
                });
            }
        };
        return descriptor;
    };
}
exports.exportedPromise = exportedPromise;
function getPromise(originalValue, config) {
    let postAction = (data) => {
        if (config && config.postActionMethod && config.shouldExecutePostAction) {
            config.postActionMethod();
        }
        if (data instanceof Error) {
            throw data;
        }
        return data;
    };
    return new Promise((onFulfilled, onRejected) => __awaiter(this, void 0, void 0, function* () {
        if (helpers_1.isPromise(originalValue)) {
            try {
                let realResult = yield originalValue;
                onFulfilled(realResult);
            }
            catch (err) {
                onRejected(err);
            }
        }
        else {
            onFulfilled(originalValue);
        }
    })).then(postAction, postAction);
}
function exported(moduleName) {
    return (target, propertyKey, descriptor) => {
        $injector.publicApi.__modules__[moduleName] = $injector.publicApi.__modules__[moduleName] || {};
        $injector.publicApi.__modules__[moduleName][propertyKey] = (...args) => {
            let originalModule = $injector.resolve(moduleName), originalMethod = originalModule[propertyKey], result = originalMethod.apply(originalModule, args);
            assert.strictEqual(helpers_1.isPromise(result), false, "Cannot use exported decorator with function returning Promise<T>.");
            return result;
        };
        return descriptor;
    };
}
exports.exported = exported;
