"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LrpWatcherPredef = void 0;
const tslib_1 = require("tslib");
const inversify_1 = require("inversify");
const listr2_1 = require("listr2");
const codiac_common_1 = require("@codiac.io/codiac-common");
const contracts_1 = require("@codiac.io/codiac-common/contracts");
const message_contracts_1 = require("../message-contracts");
const relay_queue_manager_1 = require("../relay/wsclient/relay-queue-manager");
const _ = require("lodash");
const lrp_watcher_utils_1 = require("./lrp-watcher-utils");
const ops_1 = require("../ops");
/** Triggers and renders real time status for a Long Running Process using a predefined call tree.
 *
 * That is, it fires an async request to trigger the process,
 * then monitors a socket for real time status messages,
 * and renders them in sequential order.
 */
let LrpWatcherPredef = class LrpWatcherPredef {
    constructor(msgClient, logger) {
        this.msgClient = msgClient;
        this.logger = logger;
        this.qmgr = new relay_queue_manager_1.QueueManager();
    }
    removeStatusListeners() {
        let closed = false;
        if (this.msgClient) {
            closed = closed && this.msgClient.unsubscribe(lrp_watcher_utils_1.LrpWatcherUtils.STATUS_MSG_TYPE, { subscriptionId: this.statusHandlerId });
        }
        return closed;
    }
    triggerAndWatch(trigger) {
        var _a, _b, _c;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let result = new contracts_1.Result(false);
            try {
                let socketId;
                if (!this.msgClient.isConnected()) {
                    socketId = yield this.msgClient.connect();
                }
                else {
                    socketId = (_a = this.msgClient.connectionId) !== null && _a !== void 0 ? _a : "unknown";
                }
                this.statusHandlerId = this.msgClient.subscribe(lrp_watcher_utils_1.LrpWatcherUtils.STATUS_MSG_TYPE, (err, msg, meta, raw) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                    // gotta queue these to prevent msg loss due to 
                    // client-server latency in triggering each task watcher 
                    // (server may send messages before the local client task starts listening).
                    // TODO: Convert MemoryQueue to take an ITransportMessage instead...
                    const msgForQ = {
                        action: meta.action,
                        correlationId: meta.correlationId,
                        payload: msg
                    };
                    this.qmgr.push(msgForQ, msg.opCode); // todo: consider prefixing with "<CorrelationId>."
                    return;
                }));
                // TODO: Make this the first task in the taskMap
                let ack = yield this.invokeOperation(socketId, trigger);
                let statusView = this.buildStatusView(ack.taskMap);
                let finalCtx = yield statusView.run();
                result = finalCtx.result;
            }
            catch (err) {
                this.logger.error((_b = err.message) !== null && _b !== void 0 ? _b : err.stack);
                result.error = err;
                result.message = (_c = err.message) !== null && _c !== void 0 ? _c : "Error executing operation: See logs for details";
            }
            finally {
                // await this.msgClient.disconnect();
                return result;
            }
        });
    }
    /** Throws when not successful or when no ack is returned. */
    invokeOperation(socketId, trigger) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let ackResult = yield trigger(socketId);
            if (ackResult.success != true) {
                let logMsg = "Failed invoking operation ".concat("[CID ", (_b = (_a = ackResult.output) === null || _a === void 0 ? void 0 : _a.correlationId) !== null && _b !== void 0 ? _b : "none", "]: ", (_g = (_e = (_c = ackResult.message) !== null && _c !== void 0 ? _c : (_d = ackResult.error) === null || _d === void 0 ? void 0 : _d.message) !== null && _e !== void 0 ? _e : (_f = ackResult.error) === null || _f === void 0 ? void 0 : _f.stack) !== null && _g !== void 0 ? _g : "no details available");
                this.logger.error(logMsg, ackResult.error);
                let friendlyMsg = "Failed invoking operation: ".concat((_k = (_h = ackResult.message) !== null && _h !== void 0 ? _h : (_j = ackResult.error) === null || _j === void 0 ? void 0 : _j.message) !== null && _k !== void 0 ? _k : "See logs for details");
                throw new Error(friendlyMsg);
            }
            else if (ackResult.output == undefined) {
                throw new Error("No acknowledgement received. ".concat((_l = ackResult.message) !== null && _l !== void 0 ? _l : "").trimEnd());
            }
            return ackResult.output;
        });
    }
    buildStatusView(taskMap) {
        if (taskMap == undefined)
            throw new Error("No TaskMap provided");
        let main = new listr2_1.Listr({
            title: taskMap.opIngTitle,
            task: this.toListr(taskMap)
        });
        return main;
    }
    /** DEVNOTE:  This actually returns a DELEGATE, not a concrete Listr!!!
     *            The delegate builds either a concrete task handler or a set of subtask builder delegates.
     */
    toListr(relayTask, parent) {
        let listrTaskBuilder;
        if (relayTask.tasks && relayTask.tasks.length > 0) {
            // SUBTASKS (recurse)
            listrTaskBuilder = (ctx, tx) => tx.newListr(relayTask.tasks.map(subTask => {
                return {
                    title: subTask.opIngTitle,
                    // task: async (): Promise<void> => {
                    //   await delay(3000)
                    // }
                    task: (subTask.tasks && subTask.tasks.length > 0) ? this.toListr(subTask) : this.createLeafHandler(subTask)
                };
            }), { concurrent: false });
        }
        else {
            // LEAF TASK
            listrTaskBuilder = (ctx, tx) => this.createLeafTask(relayTask);
        }
        return listrTaskBuilder;
    }
    /** Creates the task handler for a single logical task Listr.    */
    createLeafHandler(relayTask) {
        let leafHandler;
        leafHandler = (ctx, tx) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                const msgHandler = (msg) => {
                    if (msg.payload) {
                        let status = msg.payload;
                        if (status.state == message_contracts_1.OperationStateEnum.Completed) {
                            tx.task.complete();
                            // TODO: address status.completionCode and status.message
                            ctx.result = lrp_watcher_utils_1.LrpWatcherUtils.resultFromStatusMessage(status);
                            // Poor man's DRAIN
                            // TODO: drain the socket messages and the Listr tasks before we close up shop
                            setTimeout(() => {
                                this.removeStatusListeners();
                                if (ctx.result.error) {
                                    reject(ctx.result.error);
                                }
                                else {
                                    resolve();
                                }
                            }, 1000);
                        }
                        else {
                            if (status.message != undefined && !_.isEmpty(status.message)) {
                                tx.output = status.message;
                            }
                        }
                    }
                    else {
                        // no payload; ignore as a ping or heartbeat
                    }
                };
                this.qmgr.subscribeTo(relayTask.opCode, msgHandler); // promise resolutions are within the handler ^ above.
            }));
        });
        return leafHandler;
    }
    /** Creates a listr object comprised of a single logical task.    */
    createLeafTask(relayTask) {
        let leafTask = new listr2_1.Listr({
            title: relayTask.opIngTitle,
            rendererOptions: { outputBar: true },
            task: this.createLeafHandler(relayTask)
        });
        return leafTask;
    }
};
LrpWatcherPredef = tslib_1.__decorate([
    (0, inversify_1.injectable)(),
    tslib_1.__param(0, (0, inversify_1.inject)(ops_1.INTERFACES_CLI.IMessagingClient)),
    tslib_1.__param(1, (0, inversify_1.inject)(codiac_common_1.INTERFACES.IBetterLogger)),
    tslib_1.__metadata("design:paramtypes", [Object, Object])
], LrpWatcherPredef);
exports.LrpWatcherPredef = LrpWatcherPredef;
//# sourceMappingURL=lrp-watcher-predef.js.map