import { DEFAULT_TEST_ID } from "./constants.js";
import { extractOverrides, flattenHeaders, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, resolveReasoningForModel, resolveResponse, resolveStrictMode, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
import "./journal.js";
import { matchFixtureDiagnostic } from "./router.js";
import { calculateDelay, delay } from "./sse-writer.js";
import { createInterruptionSignal } from "./interruption.js";
import { buildContentWithToolCallsStreamEvents, buildTextStreamEvents, buildToolCallStreamEvents, responsesToCompletionRequest } from "./responses.js";

//#region src/ws-responses.ts
function isResponseCreateMessage(msg) {
	return typeof msg === "object" && msg !== null && msg.type === "response.create";
}
function buildErrorEvent(message, type = "invalid_request_error", code) {
	return {
		type: "error",
		error: {
			message,
			type,
			code
		}
	};
}
function handleWebSocketResponses(ws, fixtures, journal, defaults) {
	const { logger } = defaults;
	let pending = Promise.resolve();
	ws.on("message", (raw) => {
		pending = pending.then(() => processMessage(raw, ws, fixtures, journal, defaults).catch((err) => {
			const msg = err instanceof Error ? err.message : "Internal error";
			logger.error(`WebSocket responses error: ${msg}`);
			try {
				ws.send(JSON.stringify(buildErrorEvent(msg, "server_error")));
			} catch (sendErr) {
				defaults.logger.debug(`Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : "unknown"}`);
			}
		}));
	});
}
async function processMessage(raw, ws, fixtures, journal, defaults) {
	let parsed;
	try {
		parsed = JSON.parse(raw);
	} catch (parseErr) {
		const detail = parseErr instanceof Error ? parseErr.message : "unknown";
		ws.send(JSON.stringify(buildErrorEvent(`Malformed JSON: ${detail}`, "invalid_request_error", "invalid_json")));
		return;
	}
	if (!isResponseCreateMessage(parsed)) {
		ws.send(JSON.stringify(buildErrorEvent("Expected message type \"response.create\"", "invalid_request_error", "invalid_message_type")));
		return;
	}
	const completionReq = responsesToCompletionRequest({
		model: parsed.model ?? defaults.model,
		input: parsed.input ?? [],
		instructions: parsed.instructions,
		tools: parsed.tools,
		tool_choice: parsed.tool_choice,
		stream: parsed.stream,
		temperature: parsed.temperature,
		max_output_tokens: parsed.max_output_tokens
	});
	completionReq._endpointType = "chat";
	const contextHeader = defaults.upgradeHeaders?.["x-aimock-context"];
	completionReq._context = typeof contextHeader === "string" ? contextHeader : Array.isArray(contextHeader) && contextHeader.length > 0 ? contextHeader[0] : void 0;
	const testId = defaults.testId ?? DEFAULT_TEST_ID;
	const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
	if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
	if (!fixture) {
		if (resolveStrictMode(defaults.strict, defaults.upgradeHeaders)) {
			const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
			defaults.logger.error(strictNoMatchLogLine("WS", "/v1/responses", skippedBySequenceOrTurn));
			journal.add({
				method: "WS",
				path: "/v1/responses",
				headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
				body: completionReq,
				response: {
					status: 503,
					fixture: null,
					...strictOverrideField(defaults.strict, defaults.upgradeHeaders)
				}
			});
			ws.close(1008, strictMessage);
			return;
		}
		journal.add({
			method: "WS",
			path: "/v1/responses",
			headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
			body: completionReq,
			response: {
				status: 404,
				fixture: null,
				...strictOverrideField(defaults.strict, defaults.upgradeHeaders)
			}
		});
		ws.send(JSON.stringify(buildErrorEvent("No fixture matched", "invalid_request_error", "no_fixture_match")));
		return;
	}
	const response = await resolveResponse(fixture, completionReq);
	const latency = fixture.latency ?? defaults.latency;
	const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
	const effectiveStrict = resolveStrictMode(defaults.strict, defaults.upgradeHeaders);
	if (isErrorResponse(response)) {
		const status = response.status ?? 500;
		journal.add({
			method: "WS",
			path: "/v1/responses",
			headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
			body: completionReq,
			response: {
				status,
				fixture
			}
		});
		ws.send(JSON.stringify(buildErrorEvent(response.error.message, response.error.type, response.error.code)));
		return;
	}
	if (isContentWithToolCallsResponse(response)) {
		const journalEntry = journal.add({
			method: "WS",
			path: "/v1/responses",
			headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
			body: completionReq,
			response: {
				status: 200,
				fixture
			}
		});
		const events = buildContentWithToolCallsStreamEvents(response.content ?? "", response.toolCalls ?? [], completionReq.model, chunkSize, resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger), response.webSearches, extractOverrides(response), response.blocks);
		const interruption = createInterruptionSignal(fixture);
		if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
			ws.destroy();
			journalEntry.response.interrupted = true;
			journalEntry.response.interruptReason = interruption?.reason();
		}
		interruption?.cleanup();
		return;
	}
	if (isTextResponse(response)) {
		const journalEntry = journal.add({
			method: "WS",
			path: "/v1/responses",
			headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
			body: completionReq,
			response: {
				status: 200,
				fixture
			}
		});
		const events = buildTextStreamEvents(response.content, completionReq.model, chunkSize, resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger), response.webSearches, extractOverrides(response));
		const interruption = createInterruptionSignal(fixture);
		if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
			ws.destroy();
			journalEntry.response.interrupted = true;
			journalEntry.response.interruptReason = interruption?.reason();
		}
		interruption?.cleanup();
		return;
	}
	if (isToolCallResponse(response)) {
		const journalEntry = journal.add({
			method: "WS",
			path: "/v1/responses",
			headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
			body: completionReq,
			response: {
				status: 200,
				fixture
			}
		});
		const events = buildToolCallStreamEvents(response.toolCalls, completionReq.model, chunkSize, resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger), response.webSearches, extractOverrides(response));
		const interruption = createInterruptionSignal(fixture);
		if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
			ws.destroy();
			journalEntry.response.interrupted = true;
			journalEntry.response.interruptReason = interruption?.reason();
		}
		interruption?.cleanup();
		return;
	}
	journal.add({
		method: "WS",
		path: "/v1/responses",
		headers: flattenHeaders(defaults.upgradeHeaders ?? {}),
		body: completionReq,
		response: {
			status: 500,
			fixture
		}
	});
	ws.send(JSON.stringify(buildErrorEvent("Fixture response did not match any known type", "server_error")));
}
async function sendEvents(ws, events, latency, signal, onChunkSent, recordedTimings, replaySpeed) {
	let eventIndex = 0;
	for (const event of events) {
		if (ws.isClosed) return false;
		const chunkDelay = calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
		if (chunkDelay > 0) await delay(chunkDelay, signal);
		if (signal?.aborted) return false;
		if (ws.isClosed) return false;
		ws.send(JSON.stringify(event));
		eventIndex++;
		onChunkSent?.();
		if (signal?.aborted) return false;
	}
	return true;
}

//#endregion
export { handleWebSocketResponses };
//# sourceMappingURL=ws-responses.js.map