import { CronExpressionParser } from "cron-parser";
import { logger } from "../utils/globalLogger.js";
export class CronManager {
    constructor(container) {
        this.container = container;
        this.jobs = new Map();
        this.interval = null;
    }
    get aiManager() {
        return this.container.get("AIManager");
    }
    get messageManager() {
        return this.container.get("MessageManager");
    }
    start() {
        if (this.interval)
            return;
        this.interval = setInterval(() => this.checkJobs(), 60000); // Check every minute
        this.interval.unref();
    }
    stop() {
        if (this.interval) {
            clearInterval(this.interval);
            this.interval = null;
        }
    }
    createJob(job) {
        const id = Math.random().toString(36).substring(2, 11);
        const createdAt = Date.now();
        const interval = CronExpressionParser.parse(job.cron);
        const nextRunDate = interval.next().toDate();
        const nextRun = nextRunDate.getTime();
        // Calculate periodMs
        const secondRunDate = interval.next().toDate();
        const periodMs = secondRunDate.getTime() - nextRunDate.getTime();
        // Apply Jitter
        const jitteredNextRun = this.applyJitter(nextRun, periodMs, job.recurring, nextRunDate, id);
        const newJob = {
            ...job,
            id,
            createdAt,
            nextRun: jitteredNextRun,
            periodMs,
        };
        this.jobs.set(id, newJob);
        return newJob;
    }
    deleteJob(id) {
        return this.jobs.delete(id);
    }
    listJobs() {
        return Array.from(this.jobs.values());
    }
    applyJitter(nextRun, periodMs, recurring, nextRunDate, id) {
        const deterministicRandom = this.getDeterministicRandom(id);
        if (recurring) {
            // Recurring: Random delay up to 10% of period (max 15 min)
            const maxJitter = Math.min(periodMs * 0.1, 15 * 60 * 1000);
            return nextRun + deterministicRandom * maxJitter;
        }
        else {
            // One-shot: Random early fire up to 90s if scheduled on :00 or :30
            const minutes = nextRunDate.getMinutes();
            const seconds = nextRunDate.getSeconds();
            if ((minutes === 0 || minutes === 30) && seconds === 0) {
                return nextRun - deterministicRandom * 90 * 1000;
            }
        }
        return nextRun;
    }
    getDeterministicRandom(id) {
        let hash = 0;
        for (let i = 0; i < id.length; i++) {
            const char = id.charCodeAt(i);
            hash = (hash << 5) - hash + char;
            hash |= 0; // Convert to 32bit integer
        }
        // Use a simple LCG-like approach to get a value between 0 and 1
        const x = Math.sin(hash) * 10000;
        return x - Math.floor(x);
    }
    async checkJobs() {
        const now = Date.now();
        const aiManager = this.aiManager;
        const messageManager = this.messageManager;
        for (const [id, job] of this.jobs.entries()) {
            // Expiration: Recurring jobs MUST auto-expire after 7 days
            if (job.recurring && now - job.createdAt > 7 * 24 * 60 * 60 * 1000) {
                this.jobs.delete(id);
                continue;
            }
            if (now >= job.nextRun) {
                // Idle-Check: Only fire jobs if AIManager.isLoading is false
                if (aiManager.isLoading) {
                    logger?.debug(`CronManager: Skipping job ${id} because AI is busy`);
                    continue;
                }
                logger?.info(`CronManager: Firing job ${id}: ${job.prompt}`);
                // Execution
                messageManager.addUserMessage({ content: job.prompt });
                aiManager.sendAIMessage().catch((err) => {
                    logger?.error(`CronManager: Failed to execute job ${id}`, err);
                });
                if (job.recurring) {
                    // Schedule next run
                    try {
                        const interval = CronExpressionParser.parse(job.cron, {
                            currentDate: new Date(job.nextRun + 1000),
                        });
                        const nextRunDate = interval.next().toDate();
                        const nextRun = nextRunDate.getTime();
                        job.nextRun = this.applyJitter(nextRun, job.periodMs, true, nextRunDate, id);
                    }
                    catch (e) {
                        logger?.error(`CronManager: Failed to parse cron for recurring job ${id}`, e);
                        this.jobs.delete(id);
                    }
                }
                else {
                    this.jobs.delete(id);
                }
            }
        }
    }
}
