"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.matchesCron = matchesCron;
exports.simpleScheduleToCron = simpleScheduleToCron;
exports.scheduleToCron = scheduleToCron;
exports.describeSchedule = describeSchedule;
exports.getNextRunTime = getNextRunTime;
exports.findMissedRun = findMissedRun;
exports.getJobSchedules = getJobSchedules;
exports.describeAllSchedules = describeAllSchedules;
exports.getNextRunTimeAny = getNextRunTimeAny;
function parseField(field, min, max) {
    const values = [];
    for (const part of field.split(",")) {
        const stepMatch = part.match(/^(.+)\/(\d+)$/);
        const step = stepMatch ? Number.parseInt(stepMatch[2], 10) : 1;
        const range = stepMatch ? stepMatch[1] : part;
        if (range === "*") {
            for (let i = min; i <= max; i += step)
                values.push(i);
        }
        else if (range.includes("-")) {
            const [startStr, endStr] = range.split("-");
            const start = Number.parseInt(startStr, 10);
            const end = Number.parseInt(endStr, 10);
            for (let i = start; i <= end; i += step)
                values.push(i);
        }
        else {
            values.push(Number.parseInt(range, 10));
        }
    }
    return values;
}
function parseCron(expression) {
    const parts = expression.trim().split(/\s+/);
    if (parts.length !== 5) {
        throw new Error(`Invalid cron expression: expected 5 fields, got ${parts.length}`);
    }
    return {
        minute: parseField(parts[0], 0, 59),
        hour: parseField(parts[1], 0, 23),
        dayOfMonth: parseField(parts[2], 1, 31),
        month: parseField(parts[3], 1, 12),
        dayOfWeek: parseField(parts[4], 0, 6),
    };
}
function matchesCron(expression, date) {
    const fields = parseCron(expression);
    const minute = date.getMinutes();
    const hour = date.getHours();
    const dom = date.getDate();
    const month = date.getMonth() + 1;
    const dow = date.getDay();
    return (fields.minute.includes(minute) &&
        fields.hour.includes(hour) &&
        fields.dayOfMonth.includes(dom) &&
        fields.month.includes(month) &&
        fields.dayOfWeek.includes(dow));
}
function simpleScheduleToCron(schedule) {
    const time = schedule.time || "00:00";
    const [hour, minute] = time.split(":").map(Number);
    switch (schedule.frequency) {
        case "hourly":
            return `${minute} * * * *`;
        case "daily":
            return `${minute} ${hour} * * *`;
        case "weekly":
            return `${minute} ${hour} * * ${schedule.dayOfWeek ?? 0}`;
        case "monthly":
            return `${minute} ${hour} ${schedule.dayOfMonth ?? 1} * *`;
    }
}
function scheduleToCron(schedule) {
    if (schedule.type === "cron")
        return schedule.expression;
    return simpleScheduleToCron(schedule);
}
function describeSchedule(schedule) {
    if (schedule.type === "cron") {
        return `cron: ${schedule.expression}`;
    }
    const time = schedule.time || "00:00";
    const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
    switch (schedule.frequency) {
        case "hourly":
            return `Every hour at :${time.split(":")[1] || "00"}`;
        case "daily":
            return `Daily at ${time}`;
        case "weekly":
            return `Weekly on ${days[schedule.dayOfWeek ?? 0]} at ${time}`;
        case "monthly":
            return `Monthly on day ${schedule.dayOfMonth ?? 1} at ${time}`;
    }
}
function getNextRunTime(schedule, after) {
    const cron = scheduleToCron(schedule);
    const fields = parseCron(cron);
    const candidate = new Date(after.getTime());
    candidate.setSeconds(0, 0);
    candidate.setMinutes(candidate.getMinutes() + 1);
    for (let i = 0; i < 525960; i++) {
        if (fields.minute.includes(candidate.getMinutes()) &&
            fields.hour.includes(candidate.getHours()) &&
            fields.dayOfMonth.includes(candidate.getDate()) &&
            fields.month.includes(candidate.getMonth() + 1) &&
            fields.dayOfWeek.includes(candidate.getDay())) {
            return candidate;
        }
        candidate.setMinutes(candidate.getMinutes() + 1);
    }
    return new Date(after.getTime() + 86400000);
}
function findMissedRun(cronExpr, lastFiredAt, now) {
    const maxLookbackMs = 24 * 60 * 60 * 1000;
    const earliest = new Date(Math.max(lastFiredAt.getTime() + 60000, now.getTime() - maxLookbackMs));
    const candidate = new Date(earliest);
    candidate.setSeconds(0, 0);
    while (candidate.getTime() < now.getTime()) {
        if (matchesCron(cronExpr, candidate)) {
            return true;
        }
        candidate.setMinutes(candidate.getMinutes() + 1);
    }
    return false;
}
function getJobSchedules(job) {
    return job.schedules?.length ? job.schedules : [job.schedule];
}
function describeAllSchedules(schedules) {
    return schedules.map(describeSchedule).join("; ");
}
function getNextRunTimeAny(schedules, after) {
    let earliest = null;
    for (const s of schedules) {
        const next = getNextRunTime(s, after);
        if (!earliest || next.getTime() < earliest.getTime()) {
            earliest = new Date(next.getTime());
        }
    }
    return earliest ?? new Date(after.getTime() + 86400000);
}
