"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Task = void 0;
const rxjs_1 = require("rxjs");
const stream_1 = require("stream");
const event_constants_1 = require("../constants/event.constants");
const state_constants_1 = require("../constants/state.constants");
const listr_error_interface_1 = require("../interfaces/listr-error.interface");
const listr_1 = require("../listr");
const assert_1 = require("../utils/assert");
const renderer_1 = require("../utils/renderer");
const uuid_1 = require("../utils/uuid");
/**
 * Create a task from the given set of variables and make it runnable.
 */
class Task extends rxjs_1.Subject {
    constructor(listr, tasks, options, rendererOptions) {
        var _a, _b, _c, _d, _e, _f;
        super();
        this.listr = listr;
        this.tasks = tasks;
        this.options = options;
        this.rendererOptions = rendererOptions;
        /**
         * A channel for messages.
         *
         * This requires a separate channel for messages like error, skip or runtime messages to further utilize in the renderers.
         */
        this.message = {};
        // this kind of randomness is enough for task ids
        this.id = uuid_1.generateUUID();
        this.title = (_a = this.tasks) === null || _a === void 0 ? void 0 : _a.title;
        this.initialTitle = (_b = this.tasks) === null || _b === void 0 ? void 0 : _b.title;
        this.task = this.tasks.task;
        // parse functions
        this.skip = (_d = (_c = this.tasks) === null || _c === void 0 ? void 0 : _c.skip) !== null && _d !== void 0 ? _d : false;
        this.enabledFn = (_f = (_e = this.tasks) === null || _e === void 0 ? void 0 : _e.enabled) !== null && _f !== void 0 ? _f : true;
        // task options
        this.rendererTaskOptions = this.tasks.options;
        this.renderHook$ = this.listr.renderHook$;
        this.subscribe(() => {
            this.renderHook$.next();
        });
    }
    set state$(state) {
        this.state = state;
        this.next({
            type: event_constants_1.ListrEventType.STATE,
            data: state
        });
        // cancel the subtasks if this has already failed
        if (this.hasSubtasks() && this.hasFailed()) {
            for (const subtask of this.subtasks) {
                if (subtask.state === state_constants_1.ListrTaskState.PENDING) {
                    subtask.state$ = state_constants_1.ListrTaskState.FAILED;
                }
            }
        }
    }
    set output$(data) {
        this.output = data;
        this.next({
            type: event_constants_1.ListrEventType.DATA,
            data
        });
    }
    set message$(data) {
        this.message = { ...this.message, ...data };
        this.next({
            type: event_constants_1.ListrEventType.MESSAGE,
            data
        });
    }
    set title$(title) {
        this.title = title;
        this.next({
            type: event_constants_1.ListrEventType.TITLE,
            data: title
        });
    }
    /**
     * A function to check whether this task should run at all via enable.
     */
    async check(ctx) {
        // Check if a task is enabled or disabled
        if (this.state === undefined) {
            this.enabled = await assert_1.assertFunctionOrSelf(this.enabledFn, ctx);
            this.next({
                type: event_constants_1.ListrEventType.ENABLED,
                data: this.enabled
            });
        }
    }
    /** Returns whether this task has subtasks. */
    hasSubtasks() {
        var _a;
        return ((_a = this.subtasks) === null || _a === void 0 ? void 0 : _a.length) > 0;
    }
    /** Returns whether this task is in progress. */
    isPending() {
        return this.state === state_constants_1.ListrTaskState.PENDING;
    }
    /** Returns whether this task is skipped. */
    isSkipped() {
        return this.state === state_constants_1.ListrTaskState.SKIPPED;
    }
    /** Returns whether this task has been completed. */
    isCompleted() {
        return this.state === state_constants_1.ListrTaskState.COMPLETED;
    }
    /** Returns whether this task has been failed. */
    hasFailed() {
        return this.state === state_constants_1.ListrTaskState.FAILED;
    }
    /** Returns whether this task has an active rollback task going on. */
    isRollingBack() {
        return this.state === state_constants_1.ListrTaskState.ROLLING_BACK;
    }
    /** Returns whether the rollback action was successful. */
    hasRolledBack() {
        return this.state === state_constants_1.ListrTaskState.ROLLED_BACK;
    }
    /** Returns whether this task has an actively retrying task going on. */
    isRetrying() {
        return this.state === state_constants_1.ListrTaskState.RETRY;
    }
    /** Returns whether enabled function resolves to true. */
    isEnabled() {
        return this.enabled;
    }
    /** Returns whether this task actually has a title. */
    hasTitle() {
        return typeof (this === null || this === void 0 ? void 0 : this.title) === 'string';
    }
    /** Returns whether this task has a prompt inside. */
    isPrompt() {
        return this.prompt ? true : false;
    }
    /** Run the current task. */
    async run(context, wrapper) {
        var _a, _b, _c, _d, _e;
        const handleResult = (result) => {
            if (result instanceof listr_1.Listr) {
                // Detect the subtask
                // assign options
                result.options = { ...this.options, ...result.options };
                // switch to silent renderer since already rendering
                const rendererClass = renderer_1.getRenderer('silent');
                result.rendererClass = rendererClass.renderer;
                result.renderHook$.subscribe(() => {
                    this.renderHook$.next();
                });
                // assign subtasks
                this.subtasks = result.tasks;
                this.next({ type: event_constants_1.ListrEventType.SUBTASK });
                result = result.run(context);
            }
            else if (this.isPrompt()) {
                // do nothing, it is already being handled
            }
            else if (result instanceof Promise) {
                // Detect promise
                result = result.then(handleResult);
            }
            else if (result instanceof stream_1.Readable) {
                // Detect stream
                result = new Promise((resolve, reject) => {
                    result.on('data', (data) => {
                        this.output$ = data.toString();
                    });
                    result.on('error', (error) => reject(error));
                    result.on('end', () => resolve(null));
                });
            }
            else if (result instanceof rxjs_1.Observable) {
                // Detect Observable
                result = new Promise((resolve, reject) => {
                    result.subscribe({
                        next: (data) => {
                            this.output$ = data;
                        },
                        error: reject,
                        complete: resolve
                    });
                });
            }
            return result;
        };
        const startTime = Date.now();
        // finish the task first
        this.state$ = state_constants_1.ListrTaskState.PENDING;
        // check if this function wants to be skipped
        const skipped = await assert_1.assertFunctionOrSelf(this.skip, context);
        if (skipped) {
            if (typeof skipped === 'string') {
                this.message$ = { skip: skipped };
            }
            else if (this.hasTitle()) {
                this.message$ = { skip: this.title };
            }
            else {
                this.message$ = { skip: 'Skipped task without a title.' };
            }
            this.state$ = state_constants_1.ListrTaskState.SKIPPED;
            return;
        }
        try {
            // add retry functionality
            const retryCount = ((_a = this.tasks) === null || _a === void 0 ? void 0 : _a.retry) && ((_b = this.tasks) === null || _b === void 0 ? void 0 : _b.retry) > 0 ? this.tasks.retry + 1 : 1;
            for (let retries = 1; retries <= retryCount; retries++) {
                try {
                    // handle the results
                    await handleResult(this.task(context, wrapper));
                    break;
                }
                catch (e) {
                    if (retries !== retryCount) {
                        this.retry = { count: retries, withError: e };
                        this.message$ = { retry: this.retry };
                        this.title$ = this.initialTitle;
                        this.output = undefined;
                        wrapper.report(e);
                        this.state$ = state_constants_1.ListrTaskState.RETRY;
                    }
                    else {
                        throw e;
                    }
                }
            }
            if (this.isPending() || this.isRetrying()) {
                this.message$ = { duration: Date.now() - startTime };
                this.state$ = state_constants_1.ListrTaskState.COMPLETED;
            }
        }
        catch (error) {
            // catch prompt error, this was the best i could do without going crazy
            if (this.prompt instanceof listr_error_interface_1.PromptError) {
                // eslint-disable-next-line no-ex-assign
                error = new Error(this.prompt.message);
            }
            // execute the task on error function
            if ((_c = this.tasks) === null || _c === void 0 ? void 0 : _c.rollback) {
                wrapper.report(error);
                try {
                    this.state$ = state_constants_1.ListrTaskState.ROLLING_BACK;
                    await this.tasks.rollback(context, wrapper);
                    this.state$ = state_constants_1.ListrTaskState.ROLLED_BACK;
                    this.message$ = { rollback: this.title };
                }
                catch (err) {
                    this.state$ = state_constants_1.ListrTaskState.FAILED;
                    wrapper.report(err);
                    throw error;
                }
                if (((_d = this.listr.options) === null || _d === void 0 ? void 0 : _d.exitAfterRollback) !== false) {
                    // Do not exit when explicitly set to `false`
                    throw new Error(this.title);
                }
            }
            else {
                /* istanbul ignore if */
                if (error instanceof listr_error_interface_1.ListrError) {
                    return;
                }
                // mark task as failed
                this.state$ = state_constants_1.ListrTaskState.FAILED;
                // report error
                wrapper.report(error);
                if (this.listr.options.exitOnError !== false && await assert_1.assertFunctionOrSelf((_e = this.tasks) === null || _e === void 0 ? void 0 : _e.exitOnError, context) !== false) {
                    // Do not exit when explicitly set to `false`
                    throw error;
                }
            }
        }
        finally {
            // Mark the observable as completed
            this.complete();
        }
    }
}
exports.Task = Task;
