"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 _errorutils = require("../utils/error.utils");
class TaskManagerService {
    cradleService;
    toadScheduler;
    taskStates = {};
    logger;
    constructor(loggerFactory, cradleService, toadScheduler){
        this.cradleService = cradleService;
        this.toadScheduler = toadScheduler;
        this.logger = loggerFactory(TaskManagerService.name);
    }
    registerJobOrTask(registration) {
        const { id: taskId, task: serviceIdentifier, preset: schedulerOptions } = registration;
        try {
            this.validateInput(taskId, serviceIdentifier, schedulerOptions);
        } catch (e) {
            this.logger.error((0, _errorutils.errorSummary)(e), schedulerOptions);
            return;
        }
        const timedTask = this.getSafeTimedTask(taskId, serviceIdentifier);
        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("Can't 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.toadScheduler.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);
    }
    stopSchedulerTasks() {
        this.toadScheduler.stop();
    }
    validateInput(taskId, serviceIdentifier, schedulerOptions) {
        if (!taskId) {
            throw new _jobexceptions.JobValidationException("Task ID was not provided. Can't register task or schedule job.", taskId);
        }
        const serviceName = serviceIdentifier || "unknown";
        const prefix = `Job '${schedulerOptions?.name ?? serviceName}' with ID '${taskId}'`;
        if (this.taskStates[taskId]) {
            throw new _jobexceptions.JobValidationException(`${prefix} was already registered. Can't register a key twice.`, taskId);
        }
        let resolvedService;
        try {
            resolvedService = this.cradleService.resolve(serviceIdentifier);
        } 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(`${prefix} Provide 'periodic', 'runOnce', or 'runDelayed' option.`, taskId);
        }
        if (!schedulerOptions?.periodic && !!schedulerOptions.disabled) {
            throw new _jobexceptions.JobValidationException(`${prefix} Only tasks of type 'periodic' can be disabled at boot.`, taskId);
        }
        if (schedulerOptions?.runDelayed && !schedulerOptions.milliseconds && !schedulerOptions.seconds) {
            throw new _jobexceptions.JobValidationException(`${prefix} 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(`${prefix} Provide a periodic timing parameter (milliseconds|seconds|minutes|hours|days)`, taskId);
        }
    }
    getSafeTimedTask(taskId, serviceIdentifier) {
        const asyncHandler = async ()=>{
            await this.timeTask(taskId, serviceIdentifier);
        };
        return new _toadscheduler.AsyncTask(taskId, asyncHandler, this.getErrorHandler(taskId));
    }
    async timeTask(taskId, serviceIdentifier) {
        const taskState = this.taskStates[taskId];
        taskState.started = Date.now();
        const taskService = this.cradleService.resolve(serviceIdentifier);
        await taskService.run();
        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 taskState = this.taskStates[taskId];
            taskState.lastError ??= {
                time: Date.now(),
                error
            };
            this.logger.error(`Task '${taskId}' threw an exception: ${error.stack}`);
        };
    }
    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.toadScheduler.addSimpleIntervalJob(job);
        } else {
            this.logger.log(`Task '${taskId}' was marked as disabled (deferred execution).`);
        }
    }
}

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