import { createLogger } from "../../utils/logger.js";
const log = createLogger("audit-webhook");
/**
 * POSTs audit events to an HTTP endpoint. Events are batched and flushed
 * periodically. Delivery is best-effort: failed batches are re-enqueued once
 * (up to 5x batch capacity), then dropped with a warning. Failures never
 * block tool execution.
 */
export class WebhookSink {
    name = "webhook";
    buffer = [];
    inflightFlush = null;
    timer = null;
    url;
    token;
    batchSize;
    flushIntervalMs;
    constructor(options) {
        this.url = options.url;
        this.token = options.token;
        this.batchSize = options.batchSize ?? 10;
        this.flushIntervalMs = options.flushIntervalMs ?? 5000;
        this.timer = setInterval(() => {
            this.flush().catch((err) => {
                log.warn("Periodic webhook flush failed", { error: String(err) });
            });
        }, this.flushIntervalMs);
        if (this.timer.unref)
            this.timer.unref();
    }
    emit(event) {
        this.buffer.push(event);
        if (this.buffer.length >= this.batchSize) {
            this.flush().catch((err) => {
                log.warn("Webhook batch flush failed", { error: String(err) });
            });
        }
    }
    async flush() {
        if (this.buffer.length === 0)
            return;
        const p = this.doFlush();
        this.inflightFlush = p;
        try {
            await p;
        }
        finally {
            this.inflightFlush = null;
        }
    }
    async doFlush() {
        const batch = this.buffer.splice(0);
        const headers = { "Content-Type": "application/json" };
        if (this.token)
            headers["Authorization"] = `Bearer ${this.token}`;
        let delivered = false;
        try {
            const response = await fetch(this.url, {
                method: "POST",
                headers,
                body: JSON.stringify({ events: batch }),
                signal: AbortSignal.timeout(10_000),
            });
            delivered = response.ok;
            if (!response.ok) {
                log.warn("Webhook returned non-OK status", {
                    status: response.status,
                    eventCount: batch.length,
                });
            }
        }
        catch (err) {
            let host;
            try {
                host = new URL(this.url).origin;
            }
            catch { /* ignore */ }
            log.warn("Webhook POST failed", {
                ...(host ? { host } : {}),
                eventCount: batch.length,
                error: String(err),
            });
        }
        if (!delivered && this.buffer.length + batch.length <= this.batchSize * 5) {
            this.buffer.unshift(...batch);
        }
        else if (!delivered) {
            log.warn("Webhook buffer full, dropping failed batch", { eventCount: batch.length });
        }
    }
    async close() {
        if (this.timer) {
            clearInterval(this.timer);
            this.timer = null;
        }
        if (this.inflightFlush) {
            await this.inflightFlush.catch(() => { });
        }
        await this.flush();
    }
}
//# sourceMappingURL=webhook.js.map