"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __using = (stack, value, async) => {
  if (value != null) {
    if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
    var dispose, inner;
    if (async) dispose = value[__knownSymbol("asyncDispose")];
    if (dispose === void 0) {
      dispose = value[__knownSymbol("dispose")];
      if (async) inner = dispose;
    }
    if (typeof dispose !== "function") __typeError("Object not disposable");
    if (inner) dispose = function() {
      try {
        inner.call(this);
      } catch (e) {
        return Promise.reject(e);
      }
    };
    stack.push([async, dispose, value]);
  } else if (async) {
    stack.push([async]);
  }
  return value;
};
var __callDispose = (stack, error, hasError) => {
  var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
    return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
  };
  var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
  var next = (it) => {
    while (it = stack.pop()) {
      try {
        var result = it[1] && it[1].call(it[2]);
        if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
      } catch (e) {
        fail(e);
      }
    }
    if (hasError) throw error;
  };
  return next();
};
var ClientInteraction_exports = {};
__export(ClientInteraction_exports, {
  ClientInteraction: () => ClientInteraction,
  DEFAULT_MIN_INTERVAL_FLOOR: () => DEFAULT_MIN_INTERVAL_FLOOR,
  SUBSCRIPTION_MAX_INTERVAL_PUBLISHER_LIMIT: () => SUBSCRIPTION_MAX_INTERVAL_PUBLISHER_LIMIT
});
module.exports = __toCommonJS(ClientInteraction_exports);
var import_Invoke = require("#action/request/Invoke.js");
var import_Read = require("#action/request/Read.js");
var import_Specifier = require("#action/request/Specifier.js");
var import_BdxMessenger = require("#bdx/BdxMessenger.js");
var import_Mark = require("#common/Mark.js");
var import_InteractionMessenger = require("#interaction/InteractionMessenger.js");
var import_Subscription = require("#interaction/Subscription.js");
var import_PeerAddress = require("#peer/PeerAddress.js");
var import_ExchangeProvider = require("#protocol/ExchangeProvider.js");
var import_general = require("@matter/general");
var import_types = require("@matter/types");
var import_tlv = require("@matter/types/tlv");
var import_InputChunk = require("./InputChunk.js");
var import_ClientSubscriptions = require("./subscription/ClientSubscriptions.js");
var import_PeerSubscription = require("./subscription/PeerSubscription.js");
var import_SustainedSubscription = require("./subscription/SustainedSubscription.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("ClientInteraction");
const MAX_COMMAND_REF = 65535;
const SUBSCRIPTION_PROCESSING_TIME = (0, import_general.Seconds)(10);
const DEFAULT_MIN_INTERVAL_FLOOR = (0, import_general.Seconds)(1);
const SUBSCRIPTION_MAX_INTERVAL_PUBLISHER_LIMIT = (0, import_general.Minutes)(60);
const DEFAULT_TIMED_REQUEST_TIMEOUT = (0, import_general.Seconds)(10);
const DEFAULT_MINIMUM_RESPONSE_TIMEOUT_WITH_FAILSAFE = (0, import_general.Seconds)(30);
class ClientInteraction {
  environment;
  #lifetime;
  #exchangeProvider;
  #interactions = new import_general.BasicSet();
  #subscriptions;
  #abort;
  #sustainRetries;
  #address;
  #network;
  // Command batching state
  #pendingCommands = /* @__PURE__ */ new Map();
  #batchMutex;
  #batchTimer;
  #nextCommandRef = 1;
  constructor({ environment, abort, sustainRetries, exchangeProvider, address, network }) {
    this.environment = environment;
    this.#exchangeProvider = exchangeProvider ?? environment.get(import_ExchangeProvider.ExchangeProvider);
    if (environment.has(import_ClientSubscriptions.ClientSubscriptions)) {
      this.#subscriptions = environment.get(import_ClientSubscriptions.ClientSubscriptions);
    }
    this.#abort = import_general.Abort.subtask(abort);
    this.#sustainRetries = new import_general.RetrySchedule(
      environment.get(import_general.Entropy),
      import_general.RetrySchedule.Configuration(import_SustainedSubscription.SustainedSubscription.DefaultRetrySchedule, sustainRetries)
    );
    this.#address = address;
    this.#batchMutex = new import_general.Mutex(this);
    this.#network = network;
    this.#lifetime = environment.join("interactions");
    Object.defineProperties(this.#lifetime.details, {
      "# active": {
        get: () => {
          return this.#interactions.size;
        },
        enumerable: true
      }
    });
  }
  async close(reason) {
    var _stack = [];
    try {
      if (reason === void 0) {
        reason = new import_general.ClosedError("Interaction component closed");
      }
      const _closing = __using(_stack, this.#lifetime.closing());
      this.#batchTimer?.stop();
      for (const [, pending] of this.#pendingCommands) {
        pending.cleanup?.();
        pending.reject(reason);
      }
      this.#pendingCommands.clear();
      await this.#batchMutex.close();
      this.#abort(reason);
      while (this.#interactions.size) {
        await this.#interactions.deleted;
      }
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  async [Symbol.asyncDispose]() {
    await this.close();
  }
  get subscriptions() {
    if (this.#subscriptions === void 0) {
      this.#subscriptions = this.environment.get(import_ClientSubscriptions.ClientSubscriptions);
    }
    return this.#subscriptions;
  }
  /**
   * Read attributes and events.
   */
  async *read(request, session, extraAbort) {
    var _stack = [];
    try {
      const readPathsCount = (request.attributeRequests?.length ?? 0) + (request.eventRequests?.length ?? 0);
      if (readPathsCount > 9) {
        logger.info(
          "Read interactions with more than 9 paths might be not allowed by the device. Consider splitting them into several read requests."
        );
      }
      const context = __using(_stack, await this.#begin("reading", request, session, extraAbort), true);
      const { abort, messenger } = context;
      logger.info("Read", import_Mark.Mark.OUTBOUND, messenger.exchange.via, session?.logContext ?? "", request);
      await messenger.sendReadRequest(request, { abort });
      let attributeReportCount = 0;
      let eventReportCount = 0;
      const leftOverData = new Array();
      for await (const report of messenger.readDataReports({ abort })) {
        attributeReportCount += report.attributeReports?.length ?? 0;
        eventReportCount += report.eventReports?.length ?? 0;
        yield (0, import_InputChunk.InputChunk)(report, leftOverData);
        abort.throwIfAborted();
      }
      logger.info(
        "Read",
        import_Mark.Mark.INBOUND,
        messenger.exchange.via,
        messenger.exchange.diagnostics,
        import_general.Diagnostic.weak(
          attributeReportCount + eventReportCount === 0 ? "(empty)" : import_general.Diagnostic.dict({ attributes: attributeReportCount, events: eventReportCount })
        )
      );
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      var _promise = __callDispose(_stack, _error, _hasError);
      _promise && await _promise;
    }
  }
  /**
   * Probe the peer with an empty read to verify session liveness.
   *
   * Optionally sends to a different address than the session's current one via {@link addressOverride},
   * without affecting other exchanges on the session.
   *
   * @returns true if the probe succeeded, false otherwise
   */
  async probe(options) {
    if (this.#abort.aborted) {
      return false;
    }
    const abort = new import_general.Abort({ abort: [options?.abort, this.#abort] });
    let messenger;
    try {
      messenger = await import_InteractionMessenger.InteractionClientMessenger.create(this.#exchangeProvider, {
        network: options?.network ?? this.#network,
        abort,
        addressOverride: options?.addressOverride,
        requireExistingSession: true
      });
    } catch {
      abort[Symbol.dispose]();
      return false;
    }
    try {
      await messenger.sendReadRequest((0, import_Read.Read)({ fabricFilter: false }), { abort });
      for await (const _report of messenger.readDataReports({ abort })) ;
      return true;
    } catch {
      return false;
    } finally {
      await messenger.close();
      abort[Symbol.dispose]();
    }
  }
  /**
   * Write to node attributes.
   */
  async write(request, session) {
    var _stack = [];
    try {
      const context = __using(_stack, await this.#begin("writing", request, session), true);
      const { abort, messenger } = context;
      if (request.timedRequest) {
        await messenger.sendTimedRequest(request.timeout ?? DEFAULT_TIMED_REQUEST_TIMEOUT, { abort });
      }
      logger.info("Write", import_Mark.Mark.OUTBOUND, messenger.exchange.via, request);
      const response = await messenger.sendWriteCommand(request, session);
      if (request.suppressResponse) {
        return void 0;
      }
      if (!response || !response.writeResponses?.length) {
        return [];
      }
      let successCount = 0;
      let failureCount = 0;
      const result = response.writeResponses.map(
        ({
          path: { nodeId, endpointId, clusterId, attributeId, listIndex },
          status: { status, clusterStatus }
        }) => {
          if (status === import_types.Status.Success) {
            successCount++;
          } else {
            failureCount++;
          }
          return {
            kind: "attr-status",
            path: {
              nodeId,
              endpointId,
              clusterId,
              attributeId,
              listIndex
            },
            status,
            clusterStatus
          };
        }
      );
      logger.info(
        "Write",
        import_Mark.Mark.INBOUND,
        messenger.exchange.via,
        messenger.exchange.diagnostics,
        import_general.Diagnostic.weak(
          successCount + failureCount === 0 ? "(empty)" : import_general.Diagnostic.dict({ success: successCount, failure: failureCount })
        )
      );
      return result;
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      var _promise = __callDispose(_stack, _error, _hasError);
      _promise && await _promise;
    }
  }
  /**
   * Invoke a single batch of commands (internal implementation).
   */
  async *#invokeSingle(request, session) {
    var _stack = [];
    try {
      const context = __using(_stack, await this.#begin("invoking", request, session), true);
      const { abort, messenger } = context;
      if (request.timedRequest) {
        await messenger.sendTimedRequest(request.timeout ?? DEFAULT_TIMED_REQUEST_TIMEOUT, { abort });
      }
      logger.info(
        "Invoke",
        import_Mark.Mark.OUTBOUND,
        messenger.exchange.via,
        import_general.Diagnostic.asFlags({ suppressResponse: request.suppressResponse, timed: request.timedRequest }),
        request
      );
      const { expectedProcessingTime, useExtendedFailSafeMessageResponseTimeout } = request;
      const result = await messenger.sendInvokeCommand(request, {
        expectedProcessingTime: expectedProcessingTime ?? (useExtendedFailSafeMessageResponseTimeout ? DEFAULT_MINIMUM_RESPONSE_TIMEOUT_WITH_FAILSAFE : void 0),
        abort
      });
      if (!request.suppressResponse) {
        if (result && result.invokeResponses?.length) {
          const chunk = result.invokeResponses.map((response) => {
            if (response.command !== void 0) {
              const {
                commandPath: { endpointId, clusterId, commandId },
                commandRef,
                commandFields
              } = response.command;
              const cmd = request.commands.get(commandRef);
              if (!cmd) {
                throw new import_general.ImplementationError(
                  `No response schema found for commandRef ${commandRef} (endpoint ${endpointId}, cluster ${clusterId}, command ${commandId})`
                );
              }
              let responseSchema;
              if (import_Invoke.Invoke.isLegacy(cmd)) {
                responseSchema = cmd.command.responseSchema ?? import_tlv.TlvVoid;
              } else {
                const command = import_Invoke.Invoke.commandOf(cmd);
                const responseModel = command.schema.responseModel;
                responseSchema = responseModel ? (0, import_types.TlvOfModel)(responseModel) ?? import_tlv.TlvVoid : import_tlv.TlvVoid;
              }
              if (commandFields === void 0 && responseSchema !== import_tlv.TlvVoid) {
                throw new import_general.ImplementationError(
                  `No command fields found for commandRef ${commandRef} (endpoint ${endpointId}, cluster ${clusterId}, command ${commandId})`
                );
              }
              const data = commandFields === void 0 ? void 0 : responseSchema?.decodeTlv(commandFields);
              logger.info(
                "Invoke",
                import_Mark.Mark.INBOUND,
                messenger.exchange.via,
                messenger.exchange.diagnostics,
                import_general.Diagnostic.strong(import_Invoke.Invoke.isLegacy(cmd) ? "(legacy)" : (0, import_Specifier.resolvePathForSpecifier)(cmd)),
                (0, import_general.isObject)(data) ? import_general.Diagnostic.dict(data) : import_general.Diagnostic.weak("(no payload)")
              );
              const res = {
                kind: "cmd-response",
                path: {
                  endpointId,
                  clusterId,
                  commandId
                },
                commandRef,
                data
              };
              return res;
            } else if (response.status !== void 0) {
              const {
                commandPath: { endpointId, clusterId, commandId },
                commandRef,
                status: { status, clusterStatus }
              } = response.status;
              const res = {
                kind: "cmd-status",
                path: {
                  endpointId,
                  clusterId,
                  commandId
                },
                commandRef,
                status,
                clusterStatus
              };
              const cmd = request.commands.get(commandRef);
              if (cmd) {
                logger.info(
                  "Invoke",
                  import_Mark.Mark.INBOUND,
                  messenger.exchange.via,
                  messenger.exchange.diagnostics,
                  import_general.Diagnostic.strong(import_Invoke.Invoke.isLegacy(cmd) ? "(legacy)" : (0, import_Specifier.resolvePathForSpecifier)(cmd)),
                  import_general.Diagnostic.dict({ status: `${import_types.Status[status]} (${status})`, clusterStatus })
                );
              }
              return res;
            } else {
              return void 0;
            }
          }).filter((r) => r !== void 0);
          yield chunk;
        } else {
          yield [];
        }
        abort.throwIfAborted();
      }
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      var _promise = __callDispose(_stack, _error, _hasError);
      _promise && await _promise;
    }
  }
  /**
   * Split commands across multiple parallel invoke-exchanges.
   * Results are streamed as they arrive from any batch, not buffered.
   */
  async *#invokeWithSplitting(request, maxPathsPerInvoke, session) {
    const allCommands = [...request.commands.entries()];
    const batches = new Array();
    for (let i = 0; i < allCommands.length; i += maxPathsPerInvoke) {
      const batchEntries = allCommands.slice(i, i + maxPathsPerInvoke);
      batches.push(new Map(batchEntries));
    }
    const iterators = batches.map((batchCommands) => {
      const batchInvokeRequests = request.invokeRequests.filter((ir) => batchCommands.has(ir.commandRef));
      const batchRequest = {
        ...request,
        commands: batchCommands,
        invokeRequests: batchInvokeRequests,
        [import_general.Diagnostic.value]: () => import_general.Diagnostic.list(
          [...batchCommands.values()].map((cmd) => {
            const { commandRef } = cmd;
            const fields = "fields" in cmd ? cmd.fields : void 0;
            return [
              import_general.Diagnostic.strong(import_Invoke.Invoke.isLegacy(cmd) ? "(legacy)" : (0, import_Specifier.resolvePathForSpecifier)(cmd)),
              "with",
              (0, import_general.isObject)(fields) ? import_general.Diagnostic.dict(fields) : "(no payload)",
              commandRef !== void 0 ? `(ref ${commandRef})` : ""
            ];
          })
        )
      };
      return this.#invokeSingle(batchRequest, session);
    });
    yield* import_general.AsyncIterator.merge(iterators, "One or more invoke batches failed");
  }
  /**
   * Invoke one or more commands.
   *
   * When the number of commands exceeds the peer's MaxPathsPerInvoke limit (or 1 for older nodes),
   * commands are split across multiple parallel exchanges automatically.
   *
   * Single commands are automatically batched with other commands invoked in the same timer tick
   * when the device supports multiple invokes per exchange and the target is not endpoint 0.
   */
  async *invoke(request, session) {
    const maxPathsPerInvoke = this.#exchangeProvider.maxPathsPerInvoke ?? 1;
    if (request.invokeRequests.length === 1 && request.batchDuration !== false && maxPathsPerInvoke) {
      const endpointId = request.invokeRequests[0].commandPath.endpointId;
      if (endpointId !== void 0 && endpointId !== 0 && !request.timedRequest) {
        yield* this.#invokeWithBatching(request, session);
        return;
      }
    }
    const commandCount = request.commands.size;
    if (commandCount > maxPathsPerInvoke) {
      yield* this.#invokeWithSplitting(request, maxPathsPerInvoke, session);
    } else {
      yield* this.#invokeSingle(request, session);
    }
  }
  /**
   * Queue a single command for batched execution.
   * Yields the raw response entry when the batch completes.
   */
  async *#invokeWithBatching(request, session) {
    if (this.#abort.aborted) {
      throw new import_general.ImplementationError("Client interaction unavailable after close");
    }
    await this.#exchangeProvider.connect({
      connectionTimeout: session?.connectionTimeout,
      abort: session?.abort
    });
    const cmd = [...request.commands.values()][0];
    const commandRef = this.#allocateCommandRef();
    const { endpointId, clusterId, commandId } = request.invokeRequests[0].commandPath;
    const pathKey = `${endpointId}-${clusterId}-${commandId}`;
    const { promise, resolver, rejecter } = (0, import_general.createPromise)();
    const pending = {
      request: { ...cmd, commandRef },
      pathKey,
      network: request.network,
      resolve: resolver,
      reject: rejecter
    };
    this.#pendingCommands.set(commandRef, pending);
    const abortSignal = session?.abort;
    if (abortSignal) {
      if (abortSignal.aborted) {
        this.#pendingCommands.delete(commandRef);
        pending.reject(new import_general.AbortedError());
        return;
      }
      const onAbort = () => {
        pending.aborted = true;
        this.#pendingCommands.delete(commandRef);
        pending.reject(new import_general.AbortedError());
      };
      abortSignal.addEventListener("abort", onAbort, { once: true });
      pending.cleanup = () => abortSignal.removeEventListener("abort", onAbort);
    }
    const duration = request.batchDuration || import_general.Instant;
    if (this.#batchTimer?.isRunning) {
      const remaining = this.#batchTimer.interval - (this.#batchTimer.elapsed?.time ?? 0);
      if (duration < remaining) {
        this.#batchTimer.stop();
        this.#batchTimer.interval = duration;
        this.#batchTimer.start();
      }
    } else {
      if (!this.#batchTimer) {
        this.#batchTimer = import_general.Time.getTimer("invoke-batch", duration, () => this.#flushBatch());
      } else {
        this.#batchTimer.interval = duration;
      }
      this.#batchTimer.start();
    }
    try {
      const entry = await promise;
      if (entry !== void 0) {
        yield [entry];
      }
    } finally {
      pending.cleanup?.();
    }
  }
  #allocateCommandRef() {
    const startRef = this.#nextCommandRef;
    do {
      const ref = this.#nextCommandRef;
      this.#nextCommandRef = this.#nextCommandRef >= MAX_COMMAND_REF ? 1 : this.#nextCommandRef + 1;
      if (!this.#pendingCommands.has(ref)) {
        return ref;
      }
    } while (this.#nextCommandRef !== startRef);
    throw new import_general.ImplementationError("No available commandRef values");
  }
  async #flushBatch() {
    if (this.#pendingCommands.size === 0) {
      return;
    }
    const commands = new Map(this.#pendingCommands);
    this.#pendingCommands.clear();
    const batches = this.#partitionBatch(commands);
    for (const batch of batches) {
      try {
        await this.#batchMutex.produce(async () => {
          await this.#executeBatch(batch);
        });
      } catch (error) {
        for (const [, pending] of batch) {
          pending.reject(error);
        }
      }
    }
  }
  /**
   * Partition commands into sub-batches where each sub-batch has unique command paths.
   * Uses greedy fill: each command goes into the first batch that doesn't already contain its path.
   */
  #partitionBatch(commands) {
    if (commands.size <= 1) {
      return [commands];
    }
    const batches = [];
    for (const [ref, pending] of commands) {
      let placed = false;
      for (const batch of batches) {
        if (!batch.paths.has(pending.pathKey)) {
          batch.paths.add(pending.pathKey);
          batch.commands.set(ref, pending);
          placed = true;
          break;
        }
      }
      if (!placed) {
        batches.push({
          paths: /* @__PURE__ */ new Set([pending.pathKey]),
          commands: /* @__PURE__ */ new Map([[ref, pending]])
        });
      }
    }
    return batches.map((b) => b.commands);
  }
  async #executeBatch(commands) {
    try {
      for (const [ref, pending] of commands) {
        if (pending.aborted) {
          commands.delete(ref);
        }
      }
      if (commands.size === 0) {
        return;
      }
      const commandList = [...commands.values()];
      const isSingleCommand = commandList.length === 1;
      const invokeRequests = isSingleCommand ? [{ ...commandList[0].request, commandRef: void 0 }] : commandList.map((c) => c.request);
      logger.debug(`Executing ${invokeRequests.length} command(s)${isSingleCommand ? "" : " (batched)"}`);
      const batchNetwork = commandList.find((c) => c.network !== void 0)?.network;
      const batchRequest = {
        ...(0, import_Invoke.Invoke)({ commands: invokeRequests, skipValidation: true }),
        network: batchNetwork
      };
      const maxPathsPerInvoke = this.#exchangeProvider.maxPathsPerInvoke ?? 1;
      const chunks = invokeRequests.length > maxPathsPerInvoke ? this.#invokeWithSplitting(batchRequest, maxPathsPerInvoke) : this.#invokeSingle(batchRequest);
      for await (const chunk of chunks) {
        for (const entry of chunk) {
          let pending;
          if (isSingleCommand) {
            pending = commandList[0];
            commands.clear();
          } else {
            pending = commands.get(entry.commandRef);
            if (!pending) {
              if (entry.commandRef !== void 0) {
                logger.info(`Response for aborted commandRef ${entry.commandRef} discarded`);
              } else {
                logger.warn(`Received response for unknown commandRef ${entry.commandRef}`);
              }
              continue;
            }
            commands.delete(entry.commandRef);
          }
          if (pending.aborted) {
            logger.info(`Response for aborted command discarded`);
            continue;
          }
          pending.resolve(entry);
        }
      }
      for (const [, pending] of commands) {
        if (!pending.aborted) {
          pending.resolve(void 0);
        }
      }
    } catch (error) {
      for (const [, pending] of commands) {
        if (!pending.aborted) {
          pending.reject(error);
        }
      }
    }
  }
  /**
   * Subscribe to attribute values and events.
   */
  async subscribe(request, session) {
    let interactionSession = session;
    const subscriptionPathsCount = (request.attributeRequests?.length ?? 0) + (request.eventRequests?.length ?? 0);
    if (subscriptionPathsCount === 0) {
      throw new import_general.ImplementationError("When subscribing to attributes and events, at least one must be specified.");
    }
    if (subscriptionPathsCount > 3) {
      logger.info("Subscribe interactions with more than 3 paths might be not allowed by the device.");
    }
    const peer = this.#exchangeProvider.peerAddress;
    if (peer === void 0) {
      throw new import_general.ImplementationError("Subscription unavailable because peer is uncommissioned");
    }
    if (!request.keepSubscriptions) {
      for (const subscription2 of this.subscriptions) {
        if (!import_PeerAddress.PeerAddress.is(peer, subscription2.peer)) {
          continue;
        }
        logger.debug(
          `Removing subscription with ID ${import_Subscription.Subscription.idStrOf(subscription2)} because new subscription replaces it`
        );
        subscription2.close();
      }
    }
    const {
      minIntervalFloor = DEFAULT_MIN_INTERVAL_FLOOR,
      maxIntervalCeiling = SUBSCRIPTION_MAX_INTERVAL_PUBLISHER_LIMIT
    } = request;
    if (maxIntervalCeiling < minIntervalFloor) {
      throw new import_general.ImplementationError(
        `Invalid subscription request: maxIntervalCeiling (${import_general.Duration.format(
          maxIntervalCeiling
        )}) is less than minIntervalFloor (${import_general.Duration.format(minIntervalFloor)})`
      );
    }
    const subscribe = async (request2, extraAbort) => {
      var _stack = [];
      try {
        const context = __using(_stack, await this.#begin("subscribing", request2, interactionSession, extraAbort), true);
        const { abort, messenger } = context;
        logger.info(
          "Subscribe",
          import_Mark.Mark.OUTBOUND,
          messenger.exchange.via,
          import_general.Diagnostic.asFlags({ keepSubscriptions: request2.keepSubscriptions }),
          import_general.Diagnostic.dict({
            min: import_general.Duration.format(request2.minIntervalFloor),
            max: import_general.Duration.format(request2.maxIntervalCeiling)
          }),
          request2
        );
        await messenger.sendSubscribeRequest(
          {
            ...request2,
            minIntervalFloorSeconds: import_general.Seconds.of(minIntervalFloor),
            maxIntervalCeilingSeconds: import_general.Seconds.of(maxIntervalCeiling)
          },
          { abort }
        );
        await this.#handleSubscriptionResponse(request2, readChunks(messenger, abort));
        abort.throwIfAborted();
        const responseMessage = await messenger.nextMessage(import_InteractionMessenger.MessageType.SubscribeResponse, { abort });
        const response = import_types.TlvSubscribeResponse.decode(responseMessage.payload);
        const subscription2 = new import_PeerSubscription.PeerSubscription({
          lifetime: this.subscriptions,
          request: request2,
          peer,
          closed: () => this.subscriptions.delete(subscription2),
          response,
          abort,
          maxPeerResponseTime: this.maximumPeerResponseTime(SUBSCRIPTION_PROCESSING_TIME)
        });
        this.subscriptions.addPeer(subscription2);
        logger.info(
          "Subscription successful",
          import_Mark.Mark.INBOUND,
          messenger.exchange.via,
          messenger.exchange.diagnostics,
          import_general.Diagnostic.dict({
            id: import_Subscription.Subscription.idStrOf(response.subscriptionId),
            interval: import_general.Duration.format((0, import_general.Seconds)(response.maxInterval)),
            timeout: import_general.Duration.format(subscription2.timeout)
          })
        );
        return subscription2;
      } catch (_) {
        var _error = _, _hasError = true;
      } finally {
        var _promise = __callDispose(_stack, _error, _hasError);
        _promise && await _promise;
      }
    };
    const read = (request2, extraAbort, logContext) => {
      let readSession = interactionSession;
      if (logContext !== void 0) {
        readSession = {
          ...readSession,
          logContext: readSession?.logContext ? { ...readSession.logContext, ...logContext } : logContext
        };
      }
      return this.read(request2, readSession, extraAbort);
    };
    let subscription;
    if (request.sustain) {
      interactionSession = { ...interactionSession, connectionTimeout: import_general.Forever };
      subscription = new import_SustainedSubscription.SustainedSubscription({
        lifetime: this.subscriptions,
        subscribe,
        peer,
        closed: () => this.subscriptions.delete(subscription),
        request,
        abort: session?.abort,
        retries: this.#sustainRetries,
        read,
        probe: (abort) => this.probe({ abort })
      });
    } else {
      subscription = await subscribe(request);
    }
    this.subscriptions.addActive(subscription);
    return subscription;
  }
  async #handleSubscriptionResponse(request, result) {
    if (request.updated) {
      await request.updated(result);
    } else {
      for await (const _chunk of result) ;
    }
  }
  async initBdx(request, session) {
    if (this.#abort.aborted) {
      throw new import_general.ImplementationError("Client interaction unavailable after close");
    }
    this.#interactions.add(request);
    const abort = new import_general.Abort({ abort: [session?.abort, this.#abort] });
    const messenger = await import_BdxMessenger.BdxMessenger.create(this.#exchangeProvider, request.messageTimeout);
    const context = {
      abort,
      messenger,
      [Symbol.asyncDispose]: async () => {
        await messenger.close();
        this.#interactions.delete(request);
        abort[Symbol.dispose]();
      }
    };
    try {
      abort.throwIfAborted();
    } catch (e) {
      await context[Symbol.asyncDispose]();
      throw e;
    }
    return { context };
  }
  async #begin(what, request, session, extraAbort) {
    var _stack = [];
    try {
      const lifetime = __using(_stack, this.#lifetime.join(what));
      if (this.#abort.aborted) {
        throw new import_general.ImplementationError(
          `Cannot ${what} ${this.#address ?? "uncommissioned node"} because interactable is closed`
        );
      }
      const abort = new import_general.Abort({ abort: [session?.abort, this.#abort, extraAbort] });
      let messenger;
      try {
        messenger = await import_InteractionMessenger.InteractionClientMessenger.create(this.#exchangeProvider, {
          network: request.network ?? this.#network,
          abort,
          connectionTimeout: session?.connectionTimeout,
          addressOverride: request.addressOverride
        });
      } catch (e) {
        abort[Symbol.dispose]();
        throw e;
      }
      this.#interactions.add(request);
      Object.defineProperty(lifetime.details, "via", {
        get() {
          return messenger.exchange.via;
        }
      });
      const context = {
        abort,
        messenger,
        [Symbol.asyncDispose]: async () => {
          var _stack2 = [];
          try {
            const _closing = __using(_stack2, lifetime.closing());
            await messenger.close();
            this.#interactions.delete(request);
            abort[Symbol.dispose]();
          } catch (_2) {
            var _error2 = _2, _hasError2 = true;
          } finally {
            __callDispose(_stack2, _error2, _hasError2);
          }
        }
      };
      try {
        abort.throwIfAborted();
      } catch (e) {
        await context[Symbol.asyncDispose]();
        throw e;
      }
      return context;
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  get channelType() {
    return this.#exchangeProvider.channelType;
  }
  /** Calculates the current maximum response time for a message use in additional logic like timers. */
  maximumPeerResponseTime(expectedProcessingTime, includeMaximumSendingTime = false) {
    return this.#exchangeProvider.maximumPeerResponseTime(expectedProcessingTime, includeMaximumSendingTime);
  }
  get address() {
    if (this.#address === void 0) {
      throw new import_general.ImplementationError("Uncommissioned node has no peer address");
    }
    return this.#address;
  }
}
async function* readChunks(messenger, abort) {
  const leftOverData = new Array();
  for await (const report of messenger.readDataReports({ abort })) {
    yield (0, import_InputChunk.InputChunk)(report, leftOverData);
    abort.throwIfAborted();
  }
}
//# sourceMappingURL=ClientInteraction.js.map
