import { unreachable } from "../../../shared/src/asserts.js";
import { wrapWithApplicationError } from "../../../zero-protocol/src/application-error.js";
import { NoSocketOrigin, ClientClosed, Offline } from "./client-error-kind-enum.js";
import { Disconnected, NeedsAuth, Connecting, Connected, Closed, Error } from "./connection-status-enum.js";
import { isZeroError } from "./error.js";
const successResultDetails = {
  type: "success"
};
const successResult = () => successResultDetails;
function getStateDescription(error) {
  switch (error.kind) {
    case Offline:
      return "offline";
    case ClientClosed:
      return "closed";
    default:
      return "in error state";
  }
}
class MutatorProxy {
  #lc;
  #connectionManager;
  #mutationTracker;
  #mutationRejection;
  constructor(lc, connectionManager, mutationTracker) {
    this.#lc = lc;
    this.#connectionManager = connectionManager;
    this.#mutationTracker = mutationTracker;
    this.#connectionManager.subscribe(
      (state) => this.#onConnectionStateChange(state)
    );
    this.#onConnectionStateChange(connectionManager.state);
  }
  get mutationRejectionError() {
    return this.#mutationRejection?.error;
  }
  /**
   * Called when the connection state changes.
   *
   * If the connection state is disconnected, error, or closed, the
   * mutation rejection error is set and all outstanding `.server` promises in
   * the mutation tracker are rejected with the error.
   */
  #onConnectionStateChange(state) {
    if (state.name === Disconnected && state.reason.kind === NoSocketOrigin) {
      this.#mutationRejection = void 0;
      return;
    }
    switch (state.name) {
      case Disconnected:
      case Error:
      case Closed:
        this.#mutationRejection = {
          error: state.reason,
          promise: Promise.resolve(
            this.#makeZeroErrorResultDetails(state.reason)
          )
        };
        this.#mutationTracker.rejectAllOutstandingMutations(state.reason);
        break;
      case Connected:
      case Connecting:
      case NeedsAuth:
        this.#mutationRejection = void 0;
        return;
      default:
        unreachable();
    }
  }
  wrapCustomMutator(name, f) {
    return (...args) => {
      if (this.#mutationRejection) {
        const error = this.#mutationRejection.error;
        this.#lc.warn?.(
          `Mutation "${name}" rejected because Zero is ${getStateDescription(error)}. Details: ${error.message}. See also: https://zero.rocicorp.dev/docs/connection.`
        );
        return {
          client: this.#mutationRejection.promise,
          server: this.#mutationRejection.promise
        };
      }
      let result;
      const cachedMutationPromises = {};
      const wrapErrorFor = (origin) => (error) => {
        const cachedPromise = cachedMutationPromises[origin];
        if (cachedPromise) {
          return cachedPromise;
        }
        if (isZeroError(error)) {
          this.#lc.error?.(`Mutator "${name}" error on ${origin}`, error);
          const zeroErrorPromise = this.#makeZeroErrorResultDetails(error);
          cachedMutationPromises[origin] = zeroErrorPromise;
          return zeroErrorPromise;
        }
        const applicationError = wrapWithApplicationError(error);
        this.#lc.error?.(
          `Mutator "${name}" app error on ${origin}`,
          applicationError
        );
        const applicationErrorPromise = this.#makeApplicationErrorResultDetails(applicationError);
        cachedMutationPromises[origin] = applicationErrorPromise;
        return applicationErrorPromise;
      };
      try {
        result = f(...args);
      } catch (error) {
        const clientPromise = wrapErrorFor("client")(error);
        const serverPromise = wrapErrorFor("server")(error);
        return {
          client: clientPromise,
          server: serverPromise
        };
      }
      const client = this.#normalizeResultPromise(
        result.client,
        wrapErrorFor("client")
      );
      const server = this.#normalizeResultPromise(
        result.server,
        wrapErrorFor("server")
      );
      return {
        client,
        server
      };
    };
  }
  #normalizeResultPromise(promise, wrapError) {
    return promise.then(
      successResult,
      wrapError
    );
  }
  #makeZeroErrorResultDetails(zeroError) {
    return Promise.resolve({
      type: "error",
      error: {
        type: "zero",
        message: zeroError.message
      }
    });
  }
  #makeApplicationErrorResultDetails(applicationError) {
    return Promise.resolve({
      type: "error",
      error: {
        type: "app",
        message: applicationError.message,
        details: applicationError.details
      }
    });
  }
}
export {
  MutatorProxy
};
//# sourceMappingURL=mutator-proxy.js.map
