import { getDispatcherFunction, getScheduledCorrelation, getScheduledFunctions } from "./schedule.js";
import { getDurableEvent, listDurableEvents, deleteDurableEvent, lock, configurableLock } from "../../data/index.js";
import { limited } from "../../limited/index.js";
import { isSignalled } from "../../is.js";
import { SERVICE_WORKER_ID } from "../../config/index.js";
function notAborted(signal) {
    if (signal?.aborted) {
        throw new Error("Aborted");
    }
}
export async function dispatchScheduledDurableEvents(options) {
    const event = getEventOption();
    const matching = getScheduledFunctions(options);
    const dispatcher = getDispatcherFunction(options);
    const correlation = getScheduledCorrelation(options);
    const done = await configurableLock(`${correlation}:limited => dispatchEvent`);
    const controller = new AbortController();
    const { signal } = controller;
    try {
        await dispatchScheduledEvent(event);
    }
    catch (error) {
        if (!signal.aborted) {
            controller.abort(error);
        }
        throw error;
    }
    finally {
        if (!signal.aborted) {
            controller.abort("dispatchScheduledEvent finalised");
        }
        await done();
    }
    // Only repeat on success, external scheduler might have optional retries
    if (event.schedule?.repeat && event.schedule?.delay) {
        const { dispatchEvent } = await import("./event.js");
        await dispatchEvent(event);
    }
    async function dispatchScheduledEvent(event) {
        if (event.durableEventId || event.virtual) {
            const schedule = (await getDurableEvent(event)) ?? (event.virtual ? event : undefined);
            if (!isMatchingSchedule(schedule)) {
                return;
            }
            await dispatchEvent(schedule);
        }
        else if (event.type === "background") {
            await dispatchEvent(event);
        }
        else {
            const schedules = await listDurableEvents(event);
            await limited(schedules
                .filter(isMatchingSchedule)
                .map(schedule => () => dispatchEvent(schedule)));
        }
    }
    async function dispatchEvent(event) {
        let isMainThread = true;
        if (event.serviceWorkerId) {
            if (SERVICE_WORKER_ID === event.serviceWorkerId) {
                isMainThread = false;
            }
            else {
                const threads = await import("node:worker_threads");
                isMainThread = threads.isMainThread;
            }
        }
        const done = isMainThread ?
            await lock(`dispatchEvent:${event.type}:${event.durableEventId || "no-event-id"}`) :
            undefined;
        // TODO detect if this event tries to dispatch again
        try {
            if (event.serviceWorkerId) {
                if (isMainThread) {
                    const { dispatchServiceWorkerEvent } = await import("../../worker/service-worker/execute.js");
                    await dispatchServiceWorkerEvent(event);
                }
                else {
                    await dispatchEventToHandlers(event);
                }
            }
            else {
                await dispatchEventToHandlers(event);
            }
            if (!event.retain && !event.virtual && isMainThread) {
                await deleteDurableEvent(event);
            }
        }
        finally {
            await done?.();
        }
    }
    async function dispatchEventToHandlers(event) {
        const signalledEvent = {
            ...event,
            signal
        };
        if (dispatcher) {
            // This allows a dispatcher to create an event that has deeper functionality
            await dispatcher.handler(signalledEvent, dispatchLimited);
        }
        else {
            await dispatchLimited(signalledEvent);
        }
        async function dispatchLimited(event) {
            await limited(matching.map(options => () => dispatchEventToHandler(event, options)));
        }
    }
    async function dispatchEventToHandler(event, { handler }) {
        notAborted(signal);
        if (isSignalled(event) && event.signal !== signal) {
            notAborted(event.signal);
        }
        await handler(event);
    }
    function isMatchingSchedule(event) {
        if (!event)
            return false;
        if (!event.schedule)
            return true;
        const { before, after } = event.schedule;
        if (before) {
            const date = new Date(before);
            // If the current date is larger, we are no longer before
            if (Date.now() > date.getTime()) {
                return false;
            }
        }
        if (after) {
            const date = new Date(after);
            // If the current date is smaller, we are not yet after
            if (Date.now() < date.getTime()) {
                return false;
            }
        }
        return true;
    }
    function getEventOption() {
        if (options.event) {
            return options.event;
        }
        return { type: "background", virtual: true, retain: true };
    }
}
//# sourceMappingURL=dispatch-scheduled.js.map