import { trace } from "@opentelemetry/api";
import { Lock } from "@rocicorp/lock";
import { startAsyncSpan, startSpan } from "../../../otel/src/span.js";
import { version } from "../../../otel/src/version.js";
import { unreachable, assert } 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 "../services/view-syncer/view-syncer.js";
const tracer = trace.getTracer("syncer-ws-server", version);
class SyncerWsMessageHandler {
  #viewSyncer;
  #mutagen;
  #mutationLock;
  #lc;
  #clientGroupID;
  #syncContext;
  #pusher;
  constructor(lc, connectParams, viewSyncer, mutagen, pusher) {
    const {
      clientGroupID,
      clientID,
      profileID,
      wsID,
      baseCookie,
      protocolVersion,
      httpCookie,
      origin,
      userID
    } = connectParams;
    this.#viewSyncer = viewSyncer;
    this.#mutagen = mutagen;
    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.#syncContext = {
      clientID,
      profileID,
      wsID,
      baseCookie,
      protocolVersion,
      httpCookie,
      origin,
      userID
    };
  }
  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 startAsyncSpan(
          tracer,
          "connection.push",
          async () => {
            const { clientGroupID, mutations, auth: pushAuth } = 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 (pushAuth) {
              await viewSyncer.updateAuth(this.#syncContext, [
                "updateAuth",
                { auth: pushAuth }
              ]);
            }
            if (mutations.length === 0) {
              return [
                {
                  type: "ok"
                }
              ];
            }
            if (mutations[0].type === "custom") {
              assert(
                this.#pusher,
                "A ZERO_MUTATE_URL must be set in order to process custom mutations."
              );
              return [
                this.#pusher.enqueuePush(
                  this.#syncContext.clientID,
                  msg[1],
                  viewSyncer.auth?.raw,
                  this.#syncContext.httpCookie,
                  this.#syncContext.origin
                )
              ];
            }
            const auth = viewSyncer.auth;
            assert(
              auth?.type !== "opaque",
              "Only JWT auth is supported for CRUD mutations"
            );
            const ret = await this.#mutationLock.withLock(async () => {
              const errors = [];
              for (const mutation of mutations) {
                const maybeError = await this.#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" };
            });
            return [ret];
          }
        );
      }
      case "changeDesiredQueries":
        await startAsyncSpan(
          tracer,
          "connection.changeDesiredQueries",
          () => viewSyncer.changeDesiredQueries(this.#syncContext, msg)
        );
        break;
      case "updateAuth":
        await startAsyncSpan(tracer, "connection.updateAuth", async () => {
          await viewSyncer.updateAuth(this.#syncContext, msg);
        });
        break;
      case "deleteClients": {
        const deletedClientIDs = await startAsyncSpan(
          tracer,
          "connection.deleteClients",
          () => viewSyncer.deleteClients(this.#syncContext, msg)
        );
        if (this.#pusher && deletedClientIDs.length > 0) {
          await this.#pusher.deleteClientMutations(deletedClientIDs);
        }
        break;
      }
      case "initConnection": {
        const ret = [
          {
            type: "stream",
            source: "viewSyncer",
            stream: startSpan(
              tracer,
              "connection.initConnection",
              () => viewSyncer.initConnection(this.#syncContext, msg)
            )
          }
        ];
        if (this.#pusher) {
          ret.push({
            type: "stream",
            source: "pusher",
            stream: this.#pusher.initConnection(
              this.#syncContext.clientID,
              this.#syncContext.wsID,
              msg[1].userPushURL,
              msg[1].userPushHeaders,
              () => viewSyncer.clearAuth()
            )
          });
        }
        return ret;
      }
      case "closeConnection":
        break;
      case "inspect":
        await startAsyncSpan(
          tracer,
          "connection.inspect",
          () => viewSyncer.inspect(this.#syncContext, msg)
        );
        break;
      case "ackMutationResponses":
        if (this.#pusher) {
          await this.#pusher.ackMutationResponses(msg[1]);
        }
        break;
      default:
        unreachable();
    }
    return [{ type: "ok" }];
  }
}
export {
  SyncerWsMessageHandler
};
//# sourceMappingURL=syncer-ws-message-handler.js.map
