import { DEFAULT_TEST_ID } from "./constants.js";
import { clampTimeout } from "./recorder.js";

//#region src/video-proxy-shared.ts
/**
* Shared upstream-proxy helpers for the async video-generation handlers
* (OpenRouter / Veo / Grok). These are the provider-agnostic primitives every
* live record-mode proxy needs: request-base derivation behind a forwarding
* proxy, testId URL-suffix embedding, upstream timeout signals, and bounded
* idle-based body reads. They were extracted verbatim from openrouter-video.ts
* so all three handlers share one implementation rather than triplicating it.
*
* NOT included here (byte-download only, OpenRouter-specific):
* `fetchHeadersWithTimeout` and the streaming content relay. Veo serves the
* Files-API uri as-is and Grok serves `video.url` as-is — neither downloads
* video bytes — so those stay private to openrouter-video.ts.
*/
/**
* First non-empty value of a possibly array-typed, possibly comma-joined
* header. An empty header value or a leading-comma list (", host") would
* otherwise yield "" — triggering a spurious rejection warn and discarding
* valid later entries — so empty segments are skipped.
*/
function firstForwardedValue(header) {
	const raw = Array.isArray(header) ? header.join(",") : header;
	if (raw === void 0) return void 0;
	for (const segment of raw.split(",")) {
		const trimmed = segment.trim();
		if (trimmed) return trimmed;
	}
}
const FORWARDED_HOST_RE = /^[a-zA-Z0-9._-]+(:\d+)?$/;
const FORWARDED_HOST_IPV6_RE = /^\[[0-9a-fA-F:.]+\](:\d+)?$/;
function requestBase(req, logger) {
	const candidate = firstForwardedValue(req.headers["x-forwarded-proto"])?.toLowerCase();
	const proto = candidate === "http" || candidate === "https" ? candidate : "http";
	const fwdHost = firstForwardedValue(req.headers["x-forwarded-host"]);
	const rawHost = req.headers.host;
	let host = rawHost !== void 0 && (FORWARDED_HOST_RE.test(rawHost) || FORWARDED_HOST_IPV6_RE.test(rawHost)) ? rawHost : "localhost";
	if (fwdHost !== void 0) if (FORWARDED_HOST_RE.test(fwdHost) || FORWARDED_HOST_IPV6_RE.test(fwdHost)) host = fwdHost;
	else logger.warn(`x-forwarded-host value rejected, falling back to Host header: ${JSON.stringify(fwdHost.slice(0, 100))}`);
	return `${proto}://${host}`;
}
/**
* Query-string suffix embedding the request's testId into generated URLs
* (polling_url, unsigned_urls). The provider SDK fetches these URLs with
* standard Authorization but no aimock-specific headers (no x-test-id) — so
* the testId must travel in the URL for getTestId's `?testId=` fallback to
* resolve the right job scope. The default testId is omitted to keep
* single-tenant URLs clean.
*/
function testIdSuffix(testId, sep) {
	return testId === DEFAULT_TEST_ID ? "" : `${sep}testId=${encodeURIComponent(testId)}`;
}
/** Default upstream timeout for the live lifecycle proxy (matches recorder.ts). */
const DEFAULT_UPSTREAM_TIMEOUT_MS = 3e4;
/**
* Abort signal for the SMALL-JSON upstream fetches on this surface (submit,
* status poll, models listing), honoring `record.upstreamTimeoutMs` with the
* same clamp conventions as recorder.ts (non-finite / non-positive values
* fall back to the 30s default). Note the nuance (documented on
* RecordConfig.upstreamTimeoutMs): these envelope-sized fetches use the value
* as a TOTAL deadline via AbortSignal.timeout — indistinguishable from a
* socket-idle timeout for small bodies. The byte-bearing content fetches use
* `fetchHeadersWithTimeout` + `readBodyIdle` instead, which implement true
* idle semantics. An abort rejects the fetch and surfaces through the
* caller's existing failure path (502 proxy_error on submit/poll, models
* synthesis fallback).
*/
function upstreamTimeoutSignal(record) {
	return AbortSignal.timeout(clampTimeout(record?.upstreamTimeoutMs, DEFAULT_UPSTREAM_TIMEOUT_MS));
}
/**
* Buffer a fetch Response body with IDLE-based timeout semantics: a timer
* clamped from `record.bodyTimeoutMs` (30s default — same clamp as
* recorder.ts) is re-armed for every chunk, so a steadily-dripping body of
* any total duration completes and only a silent mid-body stall rejects.
* When `cap` > 0 the byte count is enforced DURING the read: on exceed the
* stream is cancelled and `{ overCap: true }` is returned with NOTHING
* oversized retained in memory — the cap is a memory guard as much as a
* disk guard.
*/
async function readBodyIdle(res, record, cap = 0) {
	const body = res.body;
	if (!body) return {
		overCap: false,
		buf: Buffer.alloc(0)
	};
	const idleMs = clampTimeout(record?.bodyTimeoutMs, DEFAULT_UPSTREAM_TIMEOUT_MS);
	const reader = body.getReader();
	const chunks = [];
	let total = 0;
	try {
		for (;;) {
			let idleTimer;
			const readPromise = reader.read();
			readPromise.catch(() => {});
			const result = await Promise.race([readPromise, new Promise((_, reject) => {
				idleTimer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Upstream response body idle for ${idleMs}ms`)), idleMs);
			})]).finally(() => clearTimeout(idleTimer));
			if (result.done) break;
			total += result.value.byteLength;
			if (cap > 0 && total > cap) return {
				overCap: true,
				bytesRead: total
			};
			chunks.push(Buffer.from(result.value));
		}
	} finally {
		reader.cancel().catch(() => {});
	}
	return {
		overCap: false,
		buf: Buffer.concat(chunks)
	};
}
/**
* Cap on the small-JSON upstream envelope bodies (submit, status poll, models
* listing) buffered before parse/relay. Envelopes are KB-scale in practice —
* 1 MB is generous headroom even for a large models listing — and they are
* buffered in full, so an unbounded `text()` would be a memory hole on a
* hostile upstream.
*/
const VIDEO_PROXY_ENVELOPE_BODY_CAP = 1024 * 1024;
/**
* Bounded read of a small-JSON upstream envelope body (submit, status poll,
* models listing): readBodyIdle's idle semantics plus the envelope cap. An
* over-cap body is a hard failure — the throw surfaces through each caller's
* existing failure path (502 proxy_error on submit/poll, models synthesis
* fallback) without retaining anything oversized in memory.
*/
async function readEnvelopeText(res, record) {
	const read = await readBodyIdle(res, record, VIDEO_PROXY_ENVELOPE_BODY_CAP);
	if (read.overCap) throw new Error(`Upstream envelope body exceeded ${VIDEO_PROXY_ENVELOPE_BODY_CAP} bytes (read aborted at ${read.bytesRead})`);
	return read.buf.toString("utf8");
}

//#endregion
export { DEFAULT_UPSTREAM_TIMEOUT_MS, readBodyIdle, readEnvelopeText, requestBase, testIdSuffix, upstreamTimeoutSignal };
//# sourceMappingURL=video-proxy-shared.js.map