/**
 * The `RunnerServer` module provides the transport-agnostic server side of the
 * cluster runner protocol. It turns the runner RPC group into handlers that
 * receive ping, notification, request, stream, and envelope messages from other
 * runners, then forwards them into `Sharding` and coordinates persisted replies
 * through `MessageStorage`.
 *
 * **Common tasks**
 *
 * - Build a runner server once an `RpcServer.Protocol` has been supplied by a
 *   transport such as HTTP, WebSocket, or sockets
 * - Provide the complete runner runtime with `Sharding` and `Runners` clients
 *   using {@link layerWithClients}
 * - Embed a cluster client without serving runner RPCs or accepting shard
 *   assignments using {@link layerClientOnly}
 *
 * **Gotchas**
 *
 * - This module does not choose a wire transport; transport-specific modules
 *   provide the `RpcServer.Protocol`
 * - Persisted requests register reply handlers in `MessageStorage` before the
 *   message is delivered to `Sharding`
 * - Client-only layers clear the configured runner address, so they can send
 *   cluster messages but do not register as shard-owning runners
 *
 * @since 4.0.0
 */
import * as Effect from "../../Effect.js";
import * as Fiber from "../../Fiber.js";
import { constant } from "../../Function.js";
import * as Layer from "../../Layer.js";
import * as Option from "../../Option.js";
import * as Queue from "../../Queue.js";
import * as RpcServer from "../rpc/RpcServer.js";
import * as Message from "./Message.js";
import * as MessageStorage from "./MessageStorage.js";
import * as Reply from "./Reply.js";
import * as RunnerHealth from "./RunnerHealth.js";
import * as Runners from "./Runners.js";
import * as Sharding from "./Sharding.js";
import { ShardingConfig } from "./ShardingConfig.js";
const constVoid = /*#__PURE__*/constant(Effect.void);
/**
 * Layer that handles runner protocol RPCs by forwarding requests to `Sharding`
 * and `MessageStorage`.
 *
 * @category layers
 * @since 4.0.0
 */
export const layerHandlers = /*#__PURE__*/Runners.Rpcs.toLayer(/*#__PURE__*/Effect.gen(function* () {
  const sharding = yield* Sharding.Sharding;
  const storage = yield* MessageStorage.MessageStorage;
  return {
    Ping: () => Effect.void,
    Notify: ({
      envelope
    }) => sharding.notify(envelope._tag === "Request" ? new Message.IncomingRequest({
      envelope,
      respond: constVoid,
      lastSentReply: Option.none()
    }) : new Message.IncomingEnvelope({
      envelope
    })),
    Effect: ({
      persisted,
      request
    }) => {
      let replyEncoded = Option.none();
      let resume = reply => {
        replyEncoded = Option.some(reply);
      };
      const message = new Message.IncomingRequest({
        envelope: request,
        lastSentReply: Option.none(),
        respond(reply) {
          resume(Effect.orDie(Reply.serialize(reply)));
          return Effect.void;
        }
      });
      if (persisted) {
        return Effect.callback(resume_ => {
          resume = resume_;
          const parent = Fiber.getCurrent();
          const onExit = exit => {
            if (exit._tag === "Failure") {
              resume(exit);
            }
          };
          const runFork = Effect.runForkWith(parent.context);
          const fiber = runFork(storage.registerReplyHandler(message));
          fiber.addObserver(onExit);
          runFork(Effect.catchTag(sharding.notify(message, constWaitUntilRead), "AlreadyProcessingMessage", () => Effect.void)).addObserver(onExit);
          return Fiber.interrupt(fiber);
        });
      }
      return Effect.andThen(sharding.send(message), Effect.callback(resume_ => {
        if (Option.isSome(replyEncoded)) {
          resume_(replyEncoded.value);
        } else {
          resume = resume_;
        }
      }));
    },
    Stream: ({
      persisted,
      request
    }) => Effect.flatMap(Queue.make(), queue => {
      const message = new Message.IncomingRequest({
        envelope: request,
        lastSentReply: Option.none(),
        respond(reply) {
          return Effect.flatMap(Reply.serialize(reply), reply => {
            Queue.offerUnsafe(queue, reply);
            return Effect.void;
          });
        }
      });
      return Effect.as(persisted ? Effect.andThen(storage.registerReplyHandler(message).pipe(Effect.onError(cause => Queue.failCause(queue, cause)), Effect.forkScoped), sharding.notify(message, constWaitUntilRead)) : sharding.send(message), queue);
    }),
    Envelope: ({
      envelope
    }) => sharding.send(new Message.IncomingEnvelope({
      envelope
    }))
  };
}));
const constWaitUntilRead = {
  waitUntilRead: true
};
/**
 * Creates the runner RPC server layer, which receives messages from other
 * runners, forwards them to the `Sharding` layer, and responds to `Ping`
 * requests.
 *
 * **When to use**
 *
 * Use when a runner process should accept runner-to-runner protocol messages
 * over a provided server `RpcServer.Protocol`.
 *
 * **Gotchas**
 *
 * This layer does not choose or provide the wire transport; provide a
 * transport-specific `RpcServer.Protocol` separately.
 *
 * @see {@link layerHandlers} for the lower-level handler layer used when the RPC server is supplied elsewhere
 * @see {@link layerWithClients} for a runner server layer that also provides the `Sharding` and `Runners` clients
 * @see {@link layerClientOnly} for embedding a cluster client without serving runner RPCs
 *
 * @category layers
 * @since 4.0.0
 */
export const layer = /*#__PURE__*/RpcServer.layer(Runners.Rpcs, {
  spanPrefix: "RunnerServer",
  disableTracing: true
}).pipe(/*#__PURE__*/Layer.provide(layerHandlers));
/**
 * Layer that provides `RunnerServer` together with `Runners` and `Sharding`
 * clients.
 *
 * @category layers
 * @since 4.0.0
 */
export const layerWithClients = /*#__PURE__*/layer.pipe(/*#__PURE__*/Layer.provideMerge(Sharding.layer), /*#__PURE__*/Layer.provideMerge(Runners.layerRpc));
/**
 * Creates a client-only `Runners` layer.
 *
 * **When to use**
 *
 * Use when you use this layer to embed a cluster client inside another Effect application
 * without registering with the ShardManager or receiving shard assignments.
 *
 * @category layers
 * @since 4.0.0
 */
export const layerClientOnly = /*#__PURE__*/Sharding.layer.pipe(/*#__PURE__*/Layer.provideMerge(Runners.layerRpc), /*#__PURE__*/Layer.provide(RunnerHealth.layerNoop), /*#__PURE__*/Layer.updateService(ShardingConfig, config => ({
  ...config,
  runnerAddress: Option.none()
})));
//# sourceMappingURL=RunnerServer.js.map