import { assert, unreachable } from "../../../shared/src/asserts.js";
import { InvalidPush } from "../../../zero-protocol/src/error-kind-enum.js";
import { ZeroCache } from "../../../zero-protocol/src/error-origin-enum.js";
import { startAsyncSpan, startSpan } from "../../../otel/src/span.js";
import { version } from "../../../otel/src/version.js";
import "../services/view-syncer/connection-context-manager.js";
import "../services/view-syncer/view-syncer.js";
import { Lock } from "@rocicorp/lock";
import { ROOT_CONTEXT, context, propagation, trace } from "@opentelemetry/api";
//#region ../zero-cache/src/workers/syncer-ws-message-handler.ts
var tracer = trace.getTracer("syncer-ws-server", version);
/**
* Wraps a function in an OTEL context extracted from a W3C traceparent header.
* This enables distributed tracing from the client through zero-cache to
* the user's API server.
*/
function withTraceparent(traceparent, fn) {
	if (!traceparent) return fn();
	const extracted = propagation.extract(ROOT_CONTEXT, { traceparent });
	return context.with(extracted, fn);
}
var SyncerWsMessageHandler = class {
	#viewSyncer;
	#mutagen;
	#mutationLock;
	#lc;
	#clientGroupID;
	#connectionSelector;
	#contextManager;
	#pusher;
	constructor(lc, connectParams, contextManager, viewSyncer, mutagen, pusher) {
		const { clientGroupID, clientID, wsID } = connectParams;
		this.#viewSyncer = viewSyncer;
		this.#mutagen = mutagen;
		this.#contextManager = contextManager;
		this.#mutationLock = new Lock();
		this.#lc = lc.withContext("connection").withContext("clientID", clientID).withContext("clientGroupID", clientGroupID).withContext("wsID", wsID);
		this.#clientGroupID = clientGroupID;
		this.#pusher = pusher;
		this.#connectionSelector = {
			clientID,
			wsID
		};
	}
	async handleMessage(msg) {
		const lc = this.#lc;
		const msgType = msg[0];
		const viewSyncer = this.#viewSyncer;
		switch (msgType) {
			case "ping":
				lc.error?.("Ping is not supported at this layer by Zero");
				break;
			case "pull":
				lc.error?.("Pull is not supported by Zero");
				break;
			case "push": return withTraceparent(msg[1].traceparent, () => startAsyncSpan(tracer, "connection.push", async () => {
				const { clientGroupID, mutations } = msg[1];
				if (clientGroupID !== this.#clientGroupID) return [{
					type: "fatal",
					error: {
						kind: InvalidPush,
						message: `clientGroupID in mutation "${clientGroupID}" does not match clientGroupID of connection "${this.#clientGroupID}`,
						origin: ZeroCache
					}
				}];
				if (mutations.length === 0) return [{ type: "ok" }];
				if (mutations[0].type === "custom") {
					if (!this.#pusher) return [{
						type: "fatal",
						error: {
							kind: InvalidPush,
							message: "A ZERO_MUTATE_URL must be set in order to process custom mutations.",
							origin: ZeroCache
						}
					}];
					return [this.#pusher.enqueuePush(this.#connectionSelector, msg[1])];
				}
				const mutagen = this.#mutagen;
				if (!mutagen) return [{
					type: "fatal",
					error: {
						kind: InvalidPush,
						message: `Support for legacy CRUD mutations is disabled`,
						origin: ZeroCache
					}
				}];
				const auth = this.#contextManager.mustGetConnectionContext(this.#connectionSelector).auth;
				assert(auth?.type !== "opaque", "Only JWT auth is supported for CRUD mutations");
				return [await this.#mutationLock.withLock(async () => {
					const errors = [];
					for (const mutation of mutations) {
						const maybeError = await mutagen.processMutation(mutation, auth?.decoded, this.#pusher !== void 0);
						if (maybeError !== void 0) errors.push({
							kind: maybeError[0],
							message: maybeError[1],
							origin: ZeroCache
						});
					}
					if (errors.length > 0) return {
						type: "transient",
						errors
					};
					return { type: "ok" };
				})];
			}));
			case "changeDesiredQueries":
				await withTraceparent(msg[1].traceparent, () => startAsyncSpan(tracer, "connection.changeDesiredQueries", () => viewSyncer.changeDesiredQueries(this.#connectionSelector, msg)));
				break;
			case "updateAuth":
				await startAsyncSpan(tracer, "connection.updateAuth", async () => {
					const initialConnection = this.#contextManager.mustGetConnectionContext(this.#connectionSelector);
					const authRevisionChanged = (await this.#contextManager.updateAuth(this.#connectionSelector, msg[1])).revision !== initialConnection.revision;
					await viewSyncer.updateAuth(this.#connectionSelector, msg, authRevisionChanged);
				});
				break;
			case "deleteClients": {
				const deletedClientIDs = await startAsyncSpan(tracer, "connection.deleteClients", () => viewSyncer.deleteClients(this.#connectionSelector, msg));
				if (this.#pusher && deletedClientIDs.length > 0) await this.#pusher.deleteClientMutations(this.#connectionSelector, deletedClientIDs);
				break;
			}
			case "initConnection":
				this.#contextManager.initConnection(this.#connectionSelector, msg[1]);
				return withTraceparent(msg[1].traceparent, () => {
					const ret = [{
						type: "stream",
						source: "viewSyncer",
						stream: startSpan(tracer, "connection.initConnection", () => viewSyncer.initConnection(this.#connectionSelector, msg))
					}];
					if (this.#pusher) ret.push({
						type: "stream",
						source: "pusher",
						stream: this.#pusher.initConnection(this.#connectionSelector)
					});
					return ret;
				});
			case "closeConnection": break;
			case "inspect":
				await startAsyncSpan(tracer, "connection.inspect", () => viewSyncer.inspect(this.#connectionSelector, msg));
				break;
			case "ackMutationResponses":
				if (this.#pusher) await this.#pusher.ackMutationResponses(this.#connectionSelector, msg[1]);
				break;
			default: unreachable(msgType);
		}
		return [{ type: "ok" }];
	}
};
//#endregion
export { SyncerWsMessageHandler };

//# sourceMappingURL=syncer-ws-message-handler.js.map