"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "TaskManagerService", {
    enumerable: true,
    get: function() {
        return TaskManagerService;
    }
});
const _toadscheduler = require("toad-scheduler");
const _awilix = require("awilix");
const _jobexceptions = require("../../exceptions/job.exceptions");
const _containertokens = require("../../container.tokens");
class TaskManagerService {
    jobScheduler;
    taskStates = {};
    cradle;
    logger;
    constructor(container){
        this.cradle = container;
        this.logger = container[_containertokens.DITokens.loggerFactory](TaskManagerService.name);
        this.jobScheduler = container[_containertokens.DITokens.toadScheduler];
    }
    validateInput(taskId, workload, schedulerOptions) {
        if (!taskId) {
            throw new _jobexceptions.JobValidationException("Task ID was not provided. Cant register task or schedule job.", taskId);
        }
        const prefix = `Job '${workload?.name || "anonymous"}' with ID '${taskId}'`;
        if (!!this.taskStates[taskId]) {
            throw new _jobexceptions.JobValidationException(`${prefix} was already registered. Cant register a key twice.`, taskId);
        }
        if (typeof workload !== "function") {
            if (typeof workload !== "string") {
                throw new _jobexceptions.JobValidationException(`${prefix} is not a callable nor a string dependency to lookup. It can't be scheduled.`, taskId);
            }
            let resolvedService;
            try {
                resolvedService = this.cradle[workload];
            } catch (e) {
                if (e instanceof _awilix.AwilixResolutionError) {
                    throw new _jobexceptions.JobValidationException(`${prefix} had an awilix dependency resolution error. It can't be scheduled without fixing this problem. Inner error:\n` + e.stack, taskId);
                } else {
                    throw new _jobexceptions.JobValidationException(`${prefix} is not a registered awilix dependency. It can't be scheduled. Error:\n` + e.stack, taskId);
                }
            }
            if (typeof resolvedService?.run !== "function") {
                throw new _jobexceptions.JobValidationException(`${prefix} was resolved but it doesn't have a 'run(..)' method to call.`, taskId);
            }
        }
        if (!schedulerOptions?.periodic && !schedulerOptions?.runOnce && !schedulerOptions?.runDelayed) {
            throw new _jobexceptions.JobValidationException(`Provide 'periodic' or 'runOnce' or 'runDelayed' option'.`, taskId);
        }
        if (!schedulerOptions?.periodic && !!schedulerOptions.disabled) {
            throw new _jobexceptions.JobValidationException(`Only tasks of type 'periodic' can be disabled at boot.`, taskId);
        }
        if (schedulerOptions?.runDelayed && !schedulerOptions.milliseconds && !schedulerOptions.seconds) {
            throw new _jobexceptions.JobValidationException(`Provide a delayed timing parameter (milliseconds|seconds)'`, taskId);
        }
        if (schedulerOptions?.periodic && !schedulerOptions.milliseconds && !schedulerOptions.seconds && !schedulerOptions.minutes && !schedulerOptions.hours && !schedulerOptions.days) {
            throw new _jobexceptions.JobValidationException(`Provide a periodic timing parameter (milliseconds|seconds|minutes|hours|days)'`, taskId);
        }
    }
    registerJobOrTask({ id: taskId, task: asyncTaskCallbackOrToken, preset: schedulerOptions }) {
        try {
            this.validateInput(taskId, asyncTaskCallbackOrToken, schedulerOptions);
        } catch (e) {
            this.logger.error(e.stack, schedulerOptions);
            return;
        }
        const timedTask = this.getSafeTimedTask(taskId, asyncTaskCallbackOrToken);
        this.taskStates[taskId] = {
            options: schedulerOptions,
            timedTask
        };
        if (schedulerOptions.runOnce) {
            timedTask.execute();
        } else if (schedulerOptions.runDelayed) {
            const delay = (schedulerOptions.milliseconds || 0) + (schedulerOptions.seconds || 0) * 1000;
            this.runTimeoutTaskInstance(taskId, delay);
        } else {
            this.#scheduleEnabledPeriodicJob(taskId);
        }
    }
    scheduleDisabledJob(taskId, failIfEnabled = true) {
        const taskState = this.getTaskState(taskId);
        const schedulerOptions = taskState?.options;
        if (schedulerOptions?.disabled !== true) {
            if (failIfEnabled) {
                throw new _jobexceptions.JobValidationException(`The requested task with ID ${taskId} was not explicitly disabled and must be running already.`, taskId);
            }
            return;
        }
        taskState.options.disabled = false;
        this.#scheduleEnabledPeriodicJob(taskId);
    }
    disableJob(taskId, failIfDisabled = true) {
        if (this.isTaskDisabled(taskId)) {
            if (failIfDisabled) {
                throw new _jobexceptions.JobValidationException("Cant disable a job which is already disabled", taskId);
            }
            return;
        }
        const taskState = this.getTaskState(taskId);
        taskState.options.disabled = true;
        taskState.job.stop();
    }
    isTaskDisabled(taskId) {
        return !!this.getTaskState(taskId).options.disabled;
    }
    deregisterTask(taskId) {
        this.getTaskState(taskId);
        delete this.taskStates[taskId];
        this.jobScheduler.removeById(taskId);
    }
    getTaskState(taskId) {
        const taskState = this.taskStates[taskId];
        if (!taskState) {
            throw new _jobexceptions.JobValidationException(`The requested task with ID ${taskId} was not registered`, taskId);
        }
        return taskState;
    }
    runTimeoutTaskInstance(taskId, timeoutMs) {
        const taskState = this.getTaskState(taskId);
        this.logger.log(`Running delayed task '${taskId}' in ${timeoutMs}ms`);
        setTimeout(()=>taskState.timedTask.execute(), timeoutMs, taskId);
    }
    getSafeTimedTask(taskId, handler) {
        const asyncHandler = async ()=>{
            await this.timeTask(taskId, handler);
        };
        return new _toadscheduler.AsyncTask(taskId, asyncHandler, this.getErrorHandler(taskId));
    }
    async timeTask(taskId, handler) {
        let taskState = this.taskStates[taskId];
        taskState.started = Date.now();
        if (typeof handler === "string") {
            const taskService = this.cradle[handler];
            await taskService.run();
        } else {
            await handler();
        }
        taskState.duration = Date.now() - taskState.started;
        if (taskState.options?.logFirstCompletion !== false && !taskState?.firstCompletion) {
            this.logger.log(`Task '${taskId}' first completion. Duration ${taskState.duration}ms`);
            taskState.firstCompletion = Date.now();
        }
    }
    getErrorHandler(taskId) {
        return (error)=>{
            const registration = this.taskStates[taskId];
            if (!registration.lastError) registration.erroredlastError = {
                time: Date.now(),
                error
            };
            this.logger.error(`Task '${taskId}' threw an exception:` + error.stack);
        };
    }
    stopSchedulerTasks() {
        this.jobScheduler.stop();
    }
    #scheduleEnabledPeriodicJob(taskId) {
        const taskState = this.getTaskState(taskId);
        if (!taskState?.timedTask || !taskState?.options) {
            throw new _jobexceptions.JobValidationException(`The requested task with ID ${taskId} was not registered properly ('timedTask' or 'options' missing).`, taskId);
        }
        const schedulerOptions = taskState?.options;
        const timedTask = taskState.timedTask;
        if (!schedulerOptions?.periodic) {
            throw new _jobexceptions.JobValidationException(`The requested task with ID ${taskId} is not periodic and cannot be enabled.`, taskId);
        }
        if (!schedulerOptions.disabled) {
            this.logger.log(`Task '${taskId}' was scheduled (runImmediately: ${!!schedulerOptions.runImmediately}).`);
            const job = new _toadscheduler.SimpleIntervalJob(schedulerOptions, timedTask);
            taskState.job = job;
            this.jobScheduler.addSimpleIntervalJob(job);
        } else {
            this.logger.log(`Task '${taskId}' was marked as disabled (deferred execution).`);
        }
    }
}

//# sourceMappingURL=task-manager.service.js.map