import { flattenHeaders, getContext, getTestId, isErrorResponse, isVideoResponse, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
import { matchFixtureDiagnostic } from "./router.js";
import { writeErrorResponse } from "./sse-writer.js";
import { applyChaos } from "./chaos.js";
import { proxyAndRecord } from "./recorder.js";
import { extractBoundary, extractFormField } from "./transcription.js";

//#region src/video.ts
const VIDEO_STATE_MAX_ENTRIES = 1e4;
const VIDEO_STATE_TTL_MS = 36e5;
/**
* A Map wrapper for video state that enforces a maximum size and per-entry TTL.
* Entries older than VIDEO_STATE_TTL_MS are lazily evicted on `get`.
* When the map exceeds VIDEO_STATE_MAX_ENTRIES on `set`, the oldest entries
* are removed to stay within bounds.
*/
var VideoStateMap = class {
	entries = /* @__PURE__ */ new Map();
	sweepTimer;
	constructor() {
		this.sweepTimer = setInterval(() => {
			const now = Date.now();
			for (const [key, entry] of this.entries) if (now - entry.createdAt > VIDEO_STATE_TTL_MS) this.entries.delete(key);
		}, 6e4);
		if (this.sweepTimer.unref) this.sweepTimer.unref();
	}
	getEntry(key) {
		const entry = this.entries.get(key);
		if (!entry) return void 0;
		if (Date.now() - entry.createdAt > VIDEO_STATE_TTL_MS) {
			this.entries.delete(key);
			return;
		}
		return {
			video: entry.video,
			createdAtUnix: Math.floor(entry.createdAt / 1e3)
		};
	}
	getCreatedAtUnix(key) {
		return this.getEntry(key)?.createdAtUnix;
	}
	set(key, video) {
		this.entries.set(key, {
			video,
			createdAt: Date.now()
		});
		if (this.entries.size > VIDEO_STATE_MAX_ENTRIES) {
			const excess = this.entries.size - VIDEO_STATE_MAX_ENTRIES;
			const iter = this.entries.keys();
			for (let i = 0; i < excess; i++) {
				const next = iter.next();
				if (!next.done) this.entries.delete(next.value);
			}
		}
	}
	delete(key) {
		return this.entries.delete(key);
	}
	clear() {
		this.entries.clear();
	}
	destroy() {
		clearInterval(this.sweepTimer);
		this.entries.clear();
	}
	get size() {
		return this.entries.size;
	}
};
async function handleVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, videoStates) {
	setCorsHeaders(res);
	const path = req.url ?? "/v1/videos";
	const method = req.method ?? "POST";
	const contentType = Array.isArray(req.headers["content-type"]) ? req.headers["content-type"][0] : req.headers["content-type"];
	const isMultipart = (contentType ?? "").toLowerCase().includes("multipart/form-data");
	let videoReq;
	if (isMultipart) {
		const boundary = extractBoundary(contentType);
		const prompt = extractFormField(raw, "prompt", boundary);
		const model = extractFormField(raw, "model", boundary);
		const size = extractFormField(raw, "size", boundary);
		const secondsRaw = extractFormField(raw, "seconds", boundary);
		videoReq = { prompt: prompt ?? "" };
		if (model !== void 0) videoReq.model = model;
		if (size !== void 0) videoReq.size = size;
		if (secondsRaw !== void 0) {
			const secondsNum = Number(secondsRaw);
			videoReq.seconds = Number.isNaN(secondsNum) ? secondsRaw : secondsNum;
		}
	} else try {
		videoReq = JSON.parse(raw);
	} catch (parseErr) {
		const detail = parseErr instanceof Error ? parseErr.message : "unknown";
		journal.add({
			method,
			path,
			headers: flattenHeaders(req.headers),
			body: null,
			response: {
				status: 400,
				fixture: null
			}
		});
		writeErrorResponse(res, 400, JSON.stringify({ error: {
			message: `Malformed JSON: ${detail}`,
			type: "invalid_request_error",
			code: "invalid_json"
		} }));
		return;
	}
	if (!videoReq.prompt) {
		journal.add({
			method,
			path,
			headers: flattenHeaders(req.headers),
			body: null,
			response: {
				status: 400,
				fixture: null
			}
		});
		writeErrorResponse(res, 400, JSON.stringify({ error: {
			message: "Missing required parameter: 'prompt'",
			type: "invalid_request_error"
		} }));
		return;
	}
	const syntheticReq = {
		model: videoReq.model ?? "sora-2",
		messages: [{
			role: "user",
			content: videoReq.prompt
		}],
		_endpointType: "video",
		_context: getContext(req)
	};
	const testId = getTestId(req);
	const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
	if (fixture) {
		journal.incrementFixtureMatchCount(fixture, fixtures, testId);
		defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
	} else defaults.logger.debug(`No fixture matched for request`);
	if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
		method,
		path,
		headers: flattenHeaders(req.headers),
		body: syntheticReq
	}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
	if (!fixture) {
		if (resolveStrictMode(defaults.strict, req.headers)) {
			const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
			defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));
			journal.add({
				method,
				path,
				headers: flattenHeaders(req.headers),
				body: syntheticReq,
				response: {
					status: 503,
					fixture: null,
					...strictOverrideField(defaults.strict, req.headers)
				}
			});
			writeErrorResponse(res, 503, JSON.stringify({ error: {
				message: strictMessage,
				type: "invalid_request_error",
				code: "no_fixture_match"
			} }));
			return;
		}
		if (defaults.record) {
			const outcome = await proxyAndRecord(req, res, syntheticReq, "openai", req.url ?? "/v1/videos", fixtures, defaults, raw);
			if (outcome === "handled_by_hook") return;
			if (outcome !== "not_configured") {
				journal.add({
					method,
					path,
					headers: flattenHeaders(req.headers),
					body: syntheticReq,
					response: {
						status: res.statusCode ?? 200,
						fixture: null,
						source: "proxy"
					}
				});
				return;
			}
		}
		journal.add({
			method,
			path,
			headers: flattenHeaders(req.headers),
			body: syntheticReq,
			response: {
				status: 404,
				fixture: null,
				...strictOverrideField(defaults.strict, req.headers)
			}
		});
		writeErrorResponse(res, 404, JSON.stringify({ error: {
			message: "No fixture matched",
			type: "invalid_request_error",
			code: "no_fixture_match"
		} }));
		return;
	}
	const response = await resolveResponse(fixture, syntheticReq);
	if (isErrorResponse(response)) {
		const status = response.status ?? 500;
		journal.add({
			method,
			path,
			headers: flattenHeaders(req.headers),
			body: syntheticReq,
			response: {
				status,
				fixture
			}
		});
		writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
		return;
	}
	if (!isVideoResponse(response)) {
		journal.add({
			method,
			path,
			headers: flattenHeaders(req.headers),
			body: syntheticReq,
			response: {
				status: 500,
				fixture
			}
		});
		writeErrorResponse(res, 500, JSON.stringify({ error: {
			message: "Fixture response is not a video type",
			type: "server_error"
		} }));
		return;
	}
	journal.add({
		method,
		path,
		headers: flattenHeaders(req.headers),
		body: syntheticReq,
		response: {
			status: 200,
			fixture
		}
	});
	const video = response.video;
	const stateKey = `${testId}:${video.id}`;
	videoStates.set(stateKey, video);
	const created_at = videoStates.getCreatedAtUnix(stateKey);
	if (video.status === "completed") {
		res.writeHead(200, { "Content-Type": "application/json" });
		res.end(JSON.stringify({
			id: video.id,
			status: video.status,
			url: video.url,
			created_at
		}));
	} else {
		res.writeHead(200, { "Content-Type": "application/json" });
		res.end(JSON.stringify({
			id: video.id,
			status: video.status,
			created_at
		}));
	}
}
function handleVideoStatus(req, res, videoId, journal, defaults, setCorsHeaders, videoStates) {
	setCorsHeaders(res);
	const path = req.url ?? `/v1/videos/${videoId}`;
	const method = req.method ?? "GET";
	if (applyChaos(res, null, defaults.chaos, req.headers, journal, {
		method,
		path,
		headers: flattenHeaders(req.headers),
		body: null
	}, "internal", defaults.registry, defaults.logger)) return;
	const stateKey = `${getTestId(req)}:${videoId}`;
	const entry = videoStates.getEntry(stateKey);
	if (!entry) {
		journal.add({
			method,
			path,
			headers: flattenHeaders(req.headers),
			body: null,
			response: {
				status: 404,
				fixture: null
			}
		});
		writeErrorResponse(res, 404, JSON.stringify({ error: {
			message: `Video ${videoId} not found`,
			type: "not_found"
		} }));
		return;
	}
	const { video, createdAtUnix: created_at } = entry;
	journal.add({
		method,
		path,
		headers: flattenHeaders(req.headers),
		body: null,
		response: {
			status: 200,
			fixture: null
		}
	});
	const body = {
		id: video.id,
		status: video.status,
		created_at
	};
	if (video.url) body.url = video.url;
	res.writeHead(200, { "Content-Type": "application/json" });
	res.end(JSON.stringify(body));
}

//#endregion
export { VideoStateMap, handleVideoCreate, handleVideoStatus };
//# sourceMappingURL=video.js.map