import { withTimeout } from "../utils/async/withTimeout.js";
import { logger } from "../utils/logger.js";
const PROXY_RESPONSE_CANCEL_TIMEOUT_MS = 1_000;
let activeRequests = 0;
let lastActivityAtMs = null;
function touchActivity() {
    lastActivityAtMs = Date.now();
}
function safelyNotifyObserver(callback) {
    try {
        callback?.();
    }
    catch (error) {
        // Observability must never alter response handling.
        if (logger.shouldLog("debug")) {
            logger.debug("[proxy] response lifecycle observer failed", {
                error: error instanceof Error ? error.message : String(error),
            });
        }
    }
}
/** Track one client-facing proxy request until its response body settles. */
export function beginProxyRequest() {
    activeRequests += 1;
    touchActivity();
    let finished = false;
    return () => {
        if (finished) {
            return;
        }
        finished = true;
        activeRequests = Math.max(0, activeRequests - 1);
        touchActivity();
    };
}
export function getProxyActivitySnapshot() {
    return {
        activeRequests,
        lastActivityAt: lastActivityAtMs === null ? null : new Date(lastActivityAtMs),
    };
}
export function isProxyActivityQuiet(snapshot, quietThresholdMs, nowMs = Date.now()) {
    if (snapshot.activeRequests > 0) {
        return false;
    }
    if (snapshot.lastActivityAt === null) {
        return true;
    }
    return nowMs - snapshot.lastActivityAt.getTime() >= quietThresholdMs;
}
/** Keep activity open until the response body completes, errors, or is cancelled. */
export function trackProxyResponse(response, finishRequest, observer) {
    if (!response.body) {
        finishRequest();
        safelyNotifyObserver(() => observer?.onTerminal?.({
            outcome: "bodyless",
            observedBodyBytes: 0,
            responseChunks: 0,
        }));
        return response;
    }
    const reader = response.body.getReader();
    let observedBodyBytes = 0;
    let responseChunks = 0;
    let settled = false;
    const settle = (outcome) => {
        if (settled) {
            return;
        }
        settled = true;
        finishRequest();
        safelyNotifyObserver(() => observer?.onTerminal?.({
            outcome,
            observedBodyBytes,
            responseChunks,
        }));
    };
    const trackedBody = new ReadableStream({
        async pull(controller) {
            try {
                const { value, done } = await reader.read();
                if (done) {
                    settle("completed");
                    controller.close();
                    return;
                }
                controller.enqueue(value);
                observedBodyBytes += value.byteLength;
                responseChunks += 1;
                if (responseChunks === 1) {
                    safelyNotifyObserver(() => observer?.onFirstChunk?.({
                        observedBodyBytes,
                        responseChunks: 1,
                    }));
                }
            }
            catch (error) {
                settle("stream_error");
                controller.error(error);
            }
        },
        async cancel(reason) {
            settle("client_cancelled");
            await withTimeout(reader.cancel(reason), PROXY_RESPONSE_CANCEL_TIMEOUT_MS, "Timed out cancelling the upstream proxy response");
        },
    });
    return new Response(trackedBody, {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
    });
}
export function resetProxyActivityForTests() {
    activeRequests = 0;
    lastActivityAtMs = null;
}
//# sourceMappingURL=proxyActivity.js.map