import { definePlugin } from "../../../utils/plugin-utils";
import { WatchTriggerInboxSchema, } from "../drainTriggerInbox/schemas";
import { requireOnMessage, resolveConcurrencyAndLease, runDrainPass, } from "../drainTriggerInbox";
import { resolveTriggerInboxId } from "../utils";
import { triggerInboxResolver } from "../../../resolvers";
import { triggersDefaults } from "../shared";
const POLL_STAGES = [
    [125, 125],
    [375, 250],
    [875, 500],
    [10000, 1000],
    [30000, 2500],
    [60000, 5000],
    [180000, 10000],
];
const DEFAULT_MAX_DRAIN_INTERVAL_MS = 60000;
function pickPollIntervalMs(elapsedMs, maxIntervalMs) {
    const stage = POLL_STAGES.find(([threshold]) => elapsedMs < threshold);
    const interval = stage ? stage[1] : maxIntervalMs;
    return Math.min(interval, maxIntervalMs);
}
function sleepOrAbort(ms, signal) {
    if (signal?.aborted)
        return Promise.resolve();
    return new Promise((resolve) => {
        let settled = false;
        const settle = () => {
            if (settled)
                return;
            settled = true;
            clearTimeout(timer);
            signal?.removeEventListener("abort", settle);
            resolve();
        };
        const timer = setTimeout(settle, ms);
        signal?.addEventListener("abort", settle, { once: true });
    });
}
/**
 * `watchTriggerInbox` is the continuous-consumption variant of
 * `drainTriggerInbox`. Same options surface, same callback shape;
 * the difference is that an empty lease triggers poll-and-wait
 * (with backoff) rather than ending the command. Resolves cleanly
 * on `signal` abort or when a callback throws
 * `ZapierAbortDrainSignal`; rejects on a fatal error or a
 * fail-fast handler error (when `continueOnError` is false).
 */
export const watchTriggerInboxPlugin = definePlugin((sdk) => {
    async function watchTriggerInbox(options) {
        const onMessage = requireOnMessage(options.onMessage);
        const { concurrency, leaseLimit } = resolveConcurrencyAndLease(options);
        const inboxId = await resolveTriggerInboxId({
            api: sdk.context.api,
            inbox: options.inbox,
        });
        const maxDrainIntervalMs = options.maxDrainIntervalSeconds !== undefined
            ? options.maxDrainIntervalSeconds * 1000
            : DEFAULT_MAX_DRAIN_INTERVAL_MS;
        let firstFetch = true;
        let lastNonEmptyAt = Date.now();
        while (!options.signal?.aborted) {
            const outcome = await runDrainPass({
                sdk,
                inboxId,
                onMessage,
                concurrency,
                leaseLimit,
                leaseSeconds: options.leaseSeconds,
                maxMessages: undefined,
                releaseOnError: options.releaseOnError ?? false,
                continueOnError: options.continueOnError ?? false,
                onError: options.onError,
                signal: options.signal,
                firstFetch,
            });
            firstFetch = false;
            if (options.signal?.aborted)
                return;
            if (outcome.abortedFromCallback)
                return;
            if (outcome.processed > 0) {
                lastNonEmptyAt = Date.now();
                continue;
            }
            const elapsedMs = Date.now() - lastNonEmptyAt;
            await sleepOrAbort(pickPollIntervalMs(elapsedMs, maxDrainIntervalMs), options.signal);
        }
    }
    return {
        watchTriggerInbox,
        context: {
            meta: {
                watchTriggerInbox: {
                    ...triggersDefaults,
                    type: "create",
                    description: "Continuously consume a trigger inbox: drain currently-available messages via onMessage, then poll with backoff for new arrivals, until aborted. Resolves cleanly on signal abort or ZapierAbortDrainSignal from a handler; rejects on fatal SDK errors or fail-fast handler errors.",
                    itemType: "void",
                    // See drainTriggerInbox: override the doc generator's default
                    // suffix so the rendered return type is Promise<void>, not
                    // Promise<voidItem>.
                    returnType: "void",
                    inputSchema: WatchTriggerInboxSchema,
                    resolvers: {
                        inbox: triggerInboxResolver,
                    },
                    packages: ["sdk"],
                },
            },
        },
    };
});
