"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.jobsRepoPlugin = exports.JobsRepo = void 0;
const queue_descriptor_1 = require("../shared/queue-descriptor");
const uuid = __importStar(require("uuid"));
const repeat_1 = require("../../shared/repeat");
const queue_repo_1 = require("./queue-repo");
const fastify_decorator_plugin_1 = require("./helper/fastify-decorator-plugin");
const config = __importStar(require("../../client/config"));
function withoutWrappingSlashes(url) {
    return withoutLeadingSlash(withoutTrailingSlash(url));
}
function withoutTrailingSlash(url) {
    if (url.endsWith("/")) {
        return url.slice(0, url.length - 1);
    }
    return url;
}
function withoutLeadingSlash(url) {
    if (url.startsWith("/")) {
        return url.slice(1);
    }
    return url;
}
class JobsRepo {
    constructor(owl, redis) {
        this.owl = owl;
        this.producer = this.owl.createProducer();
        this.queueRepo = new queue_repo_1.QueueRepo(redis, this);
    }
    static toJobDTO(job) {
        var _a, _b, _c;
        const { endpoint } = (0, queue_descriptor_1.decodeQueueDescriptor)(job.queue);
        let cron = {};
        if (((_a = job.schedule) === null || _a === void 0 ? void 0 : _a.type) === "cron") {
            const [cronExpression, cronTimezone] = (0, repeat_1.parseTimezonedCron)(job.schedule.meta);
            cron = { cron: cronExpression, cronTimezone };
        }
        return {
            id: job.id,
            endpoint,
            body: job.payload,
            runAt: job.runAt.toISOString(),
            exclusive: job.exclusive,
            retry: job.retry,
            count: job.count,
            repeat: job.schedule
                ? {
                    ...cron,
                    count: job.count,
                    every: ((_b = job.schedule) === null || _b === void 0 ? void 0 : _b.type) === "every" ? +job.schedule.meta : undefined,
                    times: (_c = job.schedule) === null || _c === void 0 ? void 0 : _c.times,
                }
                : undefined,
        };
    }
    async close() {
        await this.producer.close();
    }
    async find(byTokenId, endpoint, { count, cursor }) {
        const { newCursor, jobs } = await this.producer.scanQueue((0, queue_descriptor_1.encodeQueueDescriptor)(byTokenId, endpoint), cursor, count);
        return {
            cursor: newCursor,
            jobs: jobs.map(JobsRepo.toJobDTO),
        };
    }
    async findAll({ count, cursor }) {
        const { newCursor, jobs } = await this.producer.scanQueuePattern((0, queue_descriptor_1.encodeQueueDescriptor)("*", "*"), cursor, count);
        return {
            cursor: newCursor,
            jobs: jobs.map((job) => {
                const { tokenId } = (0, queue_descriptor_1.decodeQueueDescriptor)(job.queue);
                return {
                    ...JobsRepo.toJobDTO(job),
                    tokenId,
                };
            }),
        };
    }
    async findByTokenId(byTokenId, { count, cursor }) {
        const { newCursor, jobs } = await this.producer.scanQueuePattern((0, queue_descriptor_1.encodeQueueDescriptor)(byTokenId, "*"), cursor, count);
        return {
            cursor: newCursor,
            jobs: jobs.map(JobsRepo.toJobDTO),
        };
    }
    async queueStatsByTokenId(byTokenId) {
        let cursor = 0;
        const counts = {};
        do {
            const { newCursor, jobs } = await this.producer.scanQueuePattern((0, queue_descriptor_1.encodeQueueDescriptor)(byTokenId, "*"), cursor, 1000);
            cursor = newCursor;
            for (const job of jobs) {
                const { endpoint } = (0, queue_descriptor_1.decodeQueueDescriptor)(job.queue);
                counts[endpoint] = (counts[endpoint] || 0) + 1;
            }
        } while (cursor !== 0);
        return Object.fromEntries(Object.entries(counts).map(([endpoint, count]) => [endpoint, { count }]));
    }
    async findById(tokenId, endpoint, id) {
        const job = await this.producer.findById((0, queue_descriptor_1.encodeQueueDescriptor)(tokenId, endpoint), id);
        return job ? JobsRepo.toJobDTO(job) : undefined;
    }
    async invoke(tokenId, endpoint, id) {
        return await this.producer.invoke((0, queue_descriptor_1.encodeQueueDescriptor)(tokenId, endpoint), id);
    }
    async emptyByGetter(tokenId, getter) {
        let cursor = 0;
        const allPromises = [];
        do {
            const { cursor: newCursor, jobs } = await getter(cursor);
            cursor = newCursor;
            for (const job of jobs) {
                allPromises.push(this.delete(tokenId, job.endpoint, job.id));
            }
        } while (cursor !== 0);
        await Promise.all(allPromises);
    }
    async emptyQueue(tokenId, endpoint) {
        await this.emptyByGetter(tokenId, (cursor) => this.find(tokenId, endpoint, { cursor }));
    }
    async emptyToken(tokenId) {
        await this.emptyByGetter(tokenId, (cursor) => this.findByTokenId(tokenId, { cursor }));
    }
    async delete(tokenId, endpoint, id) {
        return await this.producer.delete((0, queue_descriptor_1.encodeQueueDescriptor)(tokenId, endpoint), id);
    }
    async enqueue(tokenId, endpoint, { body, runAt: runAtOption, id, delay, repeat, override, exclusive, retry, }) {
        if (typeof id === "undefined") {
            id = uuid.v4();
        }
        let runAt = undefined;
        if (runAtOption) {
            runAt = new Date(runAtOption);
        }
        else if (delay) {
            runAt = new Date(Date.now() + delay);
        }
        if (typeof (repeat === null || repeat === void 0 ? void 0 : repeat.times) === "number" && repeat.times < 1) {
            throw new Error("repeat.times must be positive");
        }
        let schedule_type = undefined;
        let schedule_meta = undefined;
        if (repeat === null || repeat === void 0 ? void 0 : repeat.cron) {
            schedule_type = "cron";
            if (repeat === null || repeat === void 0 ? void 0 : repeat.cronTimezone) {
                schedule_meta = (0, repeat_1.embedTimezone)(repeat.cron, repeat.cronTimezone);
            }
            else {
                schedule_meta = repeat.cron;
            }
            runAt = (0, repeat_1.cron)(runAt !== null && runAt !== void 0 ? runAt : new Date(), schedule_meta);
        }
        if (repeat === null || repeat === void 0 ? void 0 : repeat.every) {
            schedule_type = "every";
            schedule_meta = "" + repeat.every;
        }
        const createdJob = await this.producer.enqueue({
            queue: (0, queue_descriptor_1.encodeQueueDescriptor)(tokenId, endpoint),
            id,
            payload: body !== null && body !== void 0 ? body : "",
            runAt,
            exclusive,
            schedule: schedule_type
                ? {
                    type: schedule_type,
                    meta: schedule_meta,
                    times: repeat === null || repeat === void 0 ? void 0 : repeat.times,
                }
                : undefined,
            override,
            retry,
        });
        await this.queueRepo.add(endpoint, tokenId);
        return JobsRepo.toJobDTO(createdJob);
    }
    async updateCron(tokenId, { baseUrl, crons, dryRun }) {
        const deleted = [];
        const queues = await this.queueRepo.get(tokenId);
        const queuesOnSameDeployment = queues.filter((q) => q.startsWith(baseUrl));
        if (!dryRun) {
            await Promise.all(crons.map(async ({ route, schedule, timezone }) => {
                await this.enqueue(tokenId, `${config.withoutTrailingSlash(baseUrl)}/${config.withoutLeadingSlash(route)}`, {
                    id: "@cron",
                    body: "null",
                    override: true,
                    repeat: { cron: schedule, cronTimezone: timezone },
                });
            }));
        }
        const routesThatShouldPersist = crons
            .map((c) => c.route)
            .map(withoutWrappingSlashes);
        await Promise.all(queuesOnSameDeployment.map(async (queue) => {
            const route = withoutWrappingSlashes(queue.slice(baseUrl.length));
            const shouldPersist = routesThatShouldPersist.includes(route);
            if (shouldPersist) {
                return;
            }
            if (dryRun) {
                const exists = await this.findById(tokenId, queue, "@cron");
                if (exists) {
                    deleted.push(queue);
                }
            }
            else {
                const result = await this.delete(tokenId, queue, "@cron");
                if (result === "deleted") {
                    deleted.push(queue);
                }
            }
        }));
        return { deleted };
    }
    onEvent(requesterTokenId, cb) {
        const activity = this.owl.createActivity(async (event) => {
            if (event.type === "scheduled") {
                cb("scheduled", JobsRepo.toJobDTO(event.job));
                return;
            }
            const { endpoint } = (0, queue_descriptor_1.decodeQueueDescriptor)(event.queue);
            switch (event.type) {
                case "acknowledged":
                    cb("completed", { endpoint, id: event.id });
                    break;
                case "requested":
                    cb("started", { endpoint, id: event.id });
                    break;
                case "invoked":
                    cb("invoked", { endpoint, id: event.id });
                    break;
                case "rescheduled":
                    cb("rescheduled", {
                        endpoint,
                        id: event.id,
                        runAt: event.runAt.toISOString(),
                    });
                    break;
                case "deleted":
                    cb("deleted", { endpoint, id: event.id });
                    break;
            }
        }, {
            queue: (0, queue_descriptor_1.encodeQueueDescriptor)(requesterTokenId, "*"),
        });
        return () => activity.close();
    }
}
exports.JobsRepo = JobsRepo;
exports.jobsRepoPlugin = (0, fastify_decorator_plugin_1.fastifyDecoratorPlugin)("jobs", (fastify) => new JobsRepo(fastify.owl, fastify.redis));
//# sourceMappingURL=jobs-repo.js.map