/**
 * @since 4.0.0
 */
import * as Msgpackr from "msgpackr";
import * as Context from "../../Context.js";
import * as Layer from "../../Layer.js";
import * as Predicate from "../../Predicate.js";
import { hasProperty } from "../../Predicate.js";
/**
 * @since 4.0.0
 * @category serialization
 */
export class RpcSerialization extends /*#__PURE__*/Context.Service()("effect/rpc/RpcSerialization") {}
/**
 * @since 4.0.0
 * @category serialization
 */
export const json = /*#__PURE__*/RpcSerialization.of({
  contentType: "application/json",
  includesFraming: false,
  makeUnsafe: () => {
    const decoder = new TextDecoder();
    return {
      decode: bytes => {
        const decoded = JSON.parse(typeof bytes === "string" ? bytes : decoder.decode(bytes));
        return Array.isArray(decoded) ? decoded : [decoded];
      },
      encode: response => JSON.stringify(response)
    };
  }
});
/**
 * @since 4.0.0
 * @category serialization
 */
export const ndjson = /*#__PURE__*/RpcSerialization.of({
  contentType: "application/ndjson",
  includesFraming: true,
  makeUnsafe: () => {
    const decoder = new TextDecoder();
    let buffer = "";
    return {
      decode: bytes => {
        buffer += typeof bytes === "string" ? bytes : decoder.decode(bytes);
        let position = 0;
        let nlIndex = buffer.indexOf("\n", position);
        const items = [];
        while (nlIndex !== -1) {
          const item = JSON.parse(buffer.slice(position, nlIndex));
          items.push(item);
          position = nlIndex + 1;
          nlIndex = buffer.indexOf("\n", position);
        }
        buffer = buffer.slice(position);
        return items;
      },
      encode: response => {
        if (Array.isArray(response)) {
          if (response.length === 0) return undefined;
          let data = "";
          for (let i = 0; i < response.length; i++) {
            data += JSON.stringify(response[i]) + "\n";
          }
          return data;
        }
        return JSON.stringify(response) + "\n";
      }
    };
  }
});
/**
 * @since 4.0.0
 * @category serialization
 */
export const jsonRpc = options => RpcSerialization.of({
  contentType: options?.contentType ?? "application/json",
  includesFraming: false,
  makeUnsafe: () => {
    const decoder = new TextDecoder();
    const batches = new Map();
    return {
      decode: bytes => {
        const decoded = JSON.parse(typeof bytes === "string" ? bytes : decoder.decode(bytes));
        return decodeJsonRpcRaw(decoded, batches);
      },
      encode: response => {
        const encoded = encodeJsonRpcResponse(response, batches);
        return encoded && JSON.stringify(encoded);
      }
    };
  }
});
/**
 * @since 4.0.0
 * @category serialization
 */
export const ndJsonRpc = options => RpcSerialization.of({
  contentType: options?.contentType ?? "application/json-rpc",
  includesFraming: true,
  makeUnsafe: () => {
    const parser = ndjson.makeUnsafe();
    const batches = new Map();
    return {
      decode: bytes => {
        const frames = parser.decode(bytes);
        if (frames.length === 0) return [];
        const messages = [];
        for (let i = 0; i < frames.length; i++) {
          const frame = frames[i];
          messages.push(...decodeJsonRpcRaw(frame, batches));
        }
        return messages;
      },
      encode: response => {
        const encoded = encodeJsonRpcResponse(response, batches);
        return encoded && parser.encode(encoded);
      }
    };
  }
});
function decodeJsonRpcRaw(decoded, batches) {
  if (Array.isArray(decoded)) {
    const batch = {
      size: 0,
      responses: new Map()
    };
    const messages = [];
    for (let i = 0; i < decoded.length; i++) {
      const message = decodeJsonRpcMessage(decoded[i]);
      messages.push(message);
      if (message._tag === "Request") {
        batch.size++;
        batches.set(message.id, batch);
      }
    }
    return messages;
  }
  return [decodeJsonRpcMessage(decoded)];
}
function decodeJsonRpcMessage(decoded) {
  if ("method" in decoded) {
    if (Predicate.isNullish(decoded.id) && decoded.method.startsWith("@effect/rpc/")) {
      const tag = decoded.method.slice("@effect/rpc/".length);
      const requestId = decoded.params?.requestId;
      return requestId ? {
        _tag: tag,
        requestId: String(requestId)
      } : {
        _tag: tag
      };
    }
    return {
      _tag: "Request",
      id: Predicate.isNotNullish(decoded.id) ? String(decoded.id) : "",
      tag: decoded.method,
      payload: decoded.params ?? null,
      headers: decoded.headers ?? [],
      ...(decoded.spanId ? {
        traceId: decoded.traceId,
        spanId: decoded.spanId,
        sampled: decoded.sampled
      } : {})
    };
  } else if (decoded.error && decoded.error._tag === "Defect") {
    return {
      _tag: "Defect",
      defect: decoded.error.data
    };
  } else if (decoded.chunk === true) {
    return {
      _tag: "Chunk",
      requestId: String(decoded.id),
      values: decoded.result
    };
  }
  return {
    _tag: "Exit",
    requestId: String(decoded.id),
    exit: decoded.error != null ? {
      _tag: "Failure",
      cause: decoded.error._tag === "Cause" ? decoded.error.data : [{
        _tag: "Die",
        defect: decoded.error
      }]
    } : {
      _tag: "Success",
      value: decoded.result
    }
  };
}
function encodeJsonRpcRaw(response, batches) {
  if (!("requestId" in response)) {
    return encodeJsonRpcMessage(response);
  }
  const batch = batches.get(response.requestId);
  if (batch) {
    batches.delete(response.requestId);
    batch.responses.set(response.requestId, response);
    if (batch.size === batch.responses.size) {
      return Array.from(batch.responses.values(), encodeJsonRpcMessage);
    }
    return undefined;
  }
  return encodeJsonRpcMessage(response);
}
function encodeJsonRpcResponse(response, batches) {
  if (Array.isArray(response) === false) {
    return encodeJsonRpcRaw(response, batches);
  }
  if (response.length === 0) {
    return undefined;
  }
  const encoded = [];
  for (let i = 0; i < response.length; i++) {
    const current = encodeJsonRpcRaw(response[i], batches);
    if (current !== undefined) {
      encoded.push(current);
    }
  }
  if (encoded.length === 0) {
    return undefined;
  }
  if (encoded.length === 1) {
    return encoded[0];
  }
  const messages = [];
  for (let i = 0; i < encoded.length; i++) {
    const current = encoded[i];
    if (Array.isArray(current)) {
      messages.push(...current);
    } else {
      messages.push(current);
    }
  }
  return messages;
}
function encodeJsonRpcMessage(response) {
  switch (response._tag) {
    case "Request":
      return {
        jsonrpc: "2.0",
        method: response.tag,
        params: response.payload,
        id: response.id !== "" ? Number(response.id) : "",
        headers: response.headers,
        traceId: response.traceId,
        spanId: response.spanId,
        sampled: response.sampled
      };
    case "Ping":
    case "Pong":
    case "Interrupt":
    case "Ack":
    case "Eof":
      return {
        jsonrpc: "2.0",
        method: `@effect/rpc/${response._tag}`,
        params: "requestId" in response ? {
          requestId: response.requestId
        } : undefined
      };
    case "Chunk":
      return {
        jsonrpc: "2.0",
        chunk: true,
        id: Number(response.requestId),
        result: response.values
      };
    case "Exit":
      {
        if (response.exit._tag === "Success") {
          return {
            jsonrpc: "2.0",
            id: response.requestId !== "" ? Number(response.requestId) : undefined,
            result: response.exit.value
          };
        }
        const error = response.exit.cause.find(failure => failure._tag === "Fail");
        return {
          jsonrpc: "2.0",
          id: response.requestId !== "" ? Number(response.requestId) : undefined,
          error: response.exit._tag === "Failure" ? {
            _tag: "Cause",
            code: error && Predicate.hasProperty(error, "code") ? Number(error.code) : 0,
            message: error && hasProperty(error, "message") ? error.message : JSON.stringify(response.exit.cause),
            data: response.exit.cause
          } : undefined
        };
      }
    case "Defect":
      return {
        jsonrpc: "2.0",
        id: jsonRpcInternalError,
        error: {
          _tag: "Defect",
          code: 1,
          message: "A defect occurred",
          data: response.defect
        }
      };
    case "ClientProtocolError":
      return {};
  }
}
const jsonRpcInternalError = -32603;
/**
 * Create a MessagePack serialization with custom msgpackr options.
 *
 * @since 4.0.0
 * @category serialization
 */
export const makeMsgPack = options => RpcSerialization.of({
  contentType: "application/msgpack",
  includesFraming: true,
  makeUnsafe: () => {
    const unpackr = new Msgpackr.Unpackr(options);
    const packr = new Msgpackr.Packr(options);
    const encoder = new TextEncoder();
    let incomplete = undefined;
    return {
      decode(bytes) {
        let buf = typeof bytes === "string" ? encoder.encode(bytes) : bytes;
        if (incomplete !== undefined) {
          const prev = buf;
          bytes = new Uint8Array(incomplete.length + buf.length);
          bytes.set(incomplete);
          bytes.set(prev, incomplete.length);
          buf = bytes;
          incomplete = undefined;
        }
        try {
          return unpackr.unpackMultiple(buf);
        } catch (error_) {
          const error = error_;
          if (error.incomplete) {
            incomplete = buf.subarray(error.lastPosition);
            return error.values ?? [];
          }
          throw error_;
        }
      },
      encode: response => packr.pack(response)
    };
  }
});
/**
 * @since 4.0.0
 * @category serialization
 */
export const msgPack = /*#__PURE__*/makeMsgPack({
  useRecords: true
});
/**
 * A rpc serialization layer that uses JSON for serialization.
 *
 * Use this if your protocol supports framing for messages, otherwise use
 * {@link layerNdjson}.
 *
 * @since 4.0.0
 * @category serialization
 */
export const layerJson = /*#__PURE__*/Layer.succeed(RpcSerialization)(json);
/**
 * A rpc serialization layer that uses NDJSON for serialization.
 *
 * Use this if your protocol does not support framing for messages, otherwise
 * use {@link layerJson}.
 *
 * @since 4.0.0
 * @category serialization
 */
export const layerNdjson = /*#__PURE__*/Layer.succeed(RpcSerialization)(ndjson);
/**
 * A rpc serialization layer that uses JSON-RPC for serialization.
 *
 * @since 4.0.0
 * @category serialization
 */
export const layerJsonRpc = options => Layer.succeed(RpcSerialization)(jsonRpc(options));
/**
 * A rpc serialization layer that uses JSON-RPC for serialization seperated by
 * new lines.
 *
 * @since 4.0.0
 * @category serialization
 */
export const layerNdJsonRpc = options => Layer.succeed(RpcSerialization)(ndJsonRpc(options));
/**
 * A rpc serialization layer that uses MessagePack for serialization.
 *
 * MessagePack has a more compact binary format compared to JSON and NDJSON. It
 * also has better support for binary data.
 *
 * @since 4.0.0
 * @category serialization
 */
export const layerMsgPack = /*#__PURE__*/Layer.succeed(RpcSerialization)(msgPack);
//# sourceMappingURL=RpcSerialization.js.map