var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
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();
};
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { ChannelStatusResponseError } from "#securechannel/SecureChannelMessenger.js";
import { CaseClient } from "#session/case/CaseClient.js";
import {
  Abort,
  AbortedError,
  asError,
  BasicMultiplex,
  causedBy,
  Diagnostic,
  Duration,
  Heap,
  Lifetime,
  Logger,
  Millis,
  ServerAddress,
  ServerAddressSet,
  Time,
  Timestamp
} from "@matter/general";
import { GeneralStatusCode, SECURE_CHANNEL_PROTOCOL_ID, SecureChannelStatusCode } from "@matter/types";
import { TransientPeerCommunicationError } from "./PeerCommunicationError.js";
import { PeerTimingParameters } from "./PeerTimingParameters.js";
const logger = Logger.get("PeerConnection");
async function PeerConnection(peer, context, options) {
  var _stack = [];
  try {
    const via = Diagnostic.via(peer.address.toString());
    const timing = options?.timing ? PeerTimingParameters.merge(context.timing, options.timing) : context.timing;
    const overallAbort = __using(_stack, new Abort(options));
    const lifetime = __using(_stack, (peer.lifetime ?? Lifetime.process).join("connecting"));
    let network = context.networks.select(peer, options?.network);
    if (network.connect) {
      network = network.connect;
    }
    const _slot = __using(_stack, await network.semaphore.obtainSlot(overallAbort));
    if (overallAbort.aborted) {
      return;
    }
    peer.service.status.connecting(overallAbort.then(() => !!peer.sessions.size));
    const service = peer.service;
    const attempts = /* @__PURE__ */ new Map();
    let outputSession;
    let fatalError;
    const workers = new BasicMultiplex();
    const addresses = ServerAddressSet();
    const pendingAddresses = new Heap(
      ServerAddressSet.compareDesirability,
      addresses.add.bind(addresses)
    );
    let attemptingFallback;
    let lastAttemptAt;
    let addrsAttempted = 0;
    const kicker = options?.kicker;
    let lastRestartAt;
    workers.add(scheduleAttempts());
    maybeAttemptFallback();
    for await (const { kind, address } of service.addressChanges({ abort: overallAbort })) {
      switch (kind) {
        case "add":
          addAddress(address);
          break;
        case "delete":
          deleteAddress(address, "Aborting attempt because address is expired");
          maybeAttemptFallback();
          break;
      }
    }
    if (outputSession) {
      peer.service.status.isReachable = true;
    }
    overallAbort();
    await workers;
    if (fatalError) {
      throw fatalError;
    }
    return outputSession;
    async function scheduleAttempts() {
      var _stack4 = [];
      try {
        const scheduling = __using(_stack4, lifetime.join("scheduling"));
        while (true) {
          if (!pendingAddresses.size) {
            var _stack2 = [];
            try {
              const _waiting = __using(_stack2, scheduling.join("waiting for address"));
              await overallAbort.race(pendingAddresses.added);
            } catch (_2) {
              var _error2 = _2, _hasError2 = true;
            } finally {
              __callDispose(_stack2, _error2, _hasError2);
            }
          }
          if (overallAbort.aborted) {
            return;
          }
          if (lastAttemptAt !== void 0) {
            const timeSinceLastAttempt = Timestamp.delta(lastAttemptAt);
            const delayInterval = Millis(timing.delayBeforeNextAddress - timeSinceLastAttempt);
            if (delayInterval > 0) {
              var _stack3 = [];
              try {
                const _delaying = __using(_stack3, scheduling.join("delaying"));
                const changed = await overallAbort.race(
                  Abort.sleep("connection delay", overallAbort, delayInterval),
                  pendingAddresses.added,
                  pendingAddresses.deleted
                );
                if (overallAbort.aborted) {
                  return;
                }
                if (changed !== void 0) {
                  continue;
                }
              } catch (_3) {
                var _error3 = _3, _hasError3 = true;
              } finally {
                __callDispose(_stack3, _error3, _hasError3);
              }
            }
          }
          const address = pendingAddresses.shift();
          if (address) {
            initiateAttempt(address);
          }
        }
      } catch (_4) {
        var _error4 = _4, _hasError4 = true;
      } finally {
        __callDispose(_stack4, _error4, _hasError4);
      }
    }
    function addAddress(address) {
      address = addresses.add(address);
      const attempt = attempts.get(address);
      if (attempt !== void 0) {
        if (attemptingFallback && ServerAddress.isEqual(attemptingFallback, address)) {
          attemptingFallback = void 0;
          kicker?.emit("discover");
        }
        return;
      }
      pendingAddresses.add(address);
    }
    function maybeAttemptFallback() {
      if (attempts.size || pendingAddresses.size || service.addresses.size) {
        return;
      }
      attemptingFallback = peer.descriptor.operationalAddress;
      if (attemptingFallback) {
        pendingAddresses.add(attemptingFallback);
      }
    }
    function initiateAttempt(address) {
      address = addresses.add(address);
      if (attempts.has(address)) {
        return;
      }
      const addressAbort = new Abort({ abort: overallAbort });
      lastAttemptAt = Time.nowMs;
      const finished = connect(address, addressAbort).finally(() => {
        try {
          if (attempts.get(address)?.finished === finished) {
            attempts.delete(address);
            maybeAttemptFallback();
          }
        } finally {
          addressAbort.close();
        }
      });
      attempts.set(address, { abort: addressAbort, finished });
      workers.add(finished);
    }
    function deleteAddress(address, why) {
      address = addresses.add(address);
      const attempt = attempts.get(address);
      if (attempt) {
        const operationalAddress = peer.descriptor.operationalAddress;
        if (attempts.size === 1 && operationalAddress !== void 0 && ServerAddress.isEqual(operationalAddress, address)) {
          attemptingFallback = address;
          return;
        }
        debug(via, address, why);
        attempt.abort();
        attempts.delete(address);
      }
      pendingAddresses.delete(address);
    }
    function error(address, ...message) {
      logger.error(logHeaderFor(address), ...message);
    }
    function warn(address, ...message) {
      logger.warn(logHeaderFor(address), ...message);
    }
    function info(via2, address, ...message) {
      logger.info(logHeaderFor(address, via2), ...message);
    }
    function debug(via2, address, ...message) {
      logger.debug(logHeaderFor(address, via2), ...message);
    }
    function logHeaderFor(address, localVia = via) {
      return [localVia, Diagnostic.strong(ServerAddress.urlFor(address))];
    }
    async function connect(address, addressAbort) {
      var _stack2 = [];
      try {
        const addrNo = ++addrsAttempted;
        let attemptNo = 1;
        const connecting = __using(_stack2, lifetime.join("attempt"));
        connecting.details.address = ServerAddress.urlFor(address);
        if (attemptingFallback && address !== attemptingFallback) {
          deleteAddress(
            attemptingFallback,
            "Aborting attempt to last known address because device reports address change"
          );
          attemptingFallback = void 0;
        }
        while (!addressAbort.aborted) {
          try {
            await attemptOnce(address, addressAbort, connecting, addrNo, attemptNo++);
          } catch (e) {
            await handleConnectionError(asError(e), address, addressAbort, connecting);
          }
        }
      } catch (_2) {
        var _error2 = _2, _hasError2 = true;
      } finally {
        __callDispose(_stack2, _error2, _hasError2);
      }
    }
    async function attemptOnce(address, addressAbort, attemptLifetime, addrNo, attemptNo) {
      var _stack4 = [];
      try {
        let socket;
        {
          var _stack2 = [];
          try {
            const _opening = __using(_stack2, attemptLifetime.join("opening socket"));
            socket = await context.openSocket(address, addressAbort);
            if (socket === void 0) {
              return;
            }
          } catch (_2) {
            var _error2 = _2, _hasError2 = true;
          } finally {
            __callDispose(_stack2, _error2, _hasError2);
          }
        }
        const isFallback = attemptingFallback && addrsAttempted > 1;
        const unsecuredSession = __using(_stack4, context.sessions.createUnsecuredSession({
          channel: socket,
          sessionParameters: peer.sessionParameters,
          isInitiator: true
        }), true);
        const exchange = __using(_stack4, PeerConnection.createExchange(peer, context.exchanges, unsecuredSession, network), true);
        info(
          Diagnostic.via(`${peer.address.toString()}${exchange.via}`),
          address,
          "Connecting",
          Diagnostic.dict({
            "addr #": addrNo,
            "attempt #": attemptNo,
            "connect time": Duration.format(Timestamp.delta(lifetime.startedAt)),
            "addr time": Duration.format(Timestamp.delta(attemptLifetime.startedAt))
          }),
          Diagnostic.asFlags({
            [network.id]: true,
            fallback: address === attemptingFallback
          })
        );
        const caseClient = new CaseClient(context.sessions);
        const fabric = context.sessions.fabricFor(peer.address);
        let kick;
        const localAbort = __using(_stack4, new Abort({ abort: addressAbort }));
        try {
          var _stack3 = [];
          try {
            const _pairing = __using(_stack3, attemptLifetime.join("pairing"));
            kick = kicker?.use((origin) => {
              if (exchange.retransmissionCount < context.timing.kickMinRetransmissions) {
                return;
              }
              const threshold = origin === "discover" ? context.timing.kickRestartCooldown.addressChange : context.timing.kickRestartCooldown.connect;
              if (lastRestartAt === void 0 || Timestamp.delta(lastRestartAt) >= threshold) {
                info(via, address, `Restarting exchange on "${origin}" kick`);
                lastRestartAt = Time.nowMs;
                localAbort();
              } else {
                debug(
                  via,
                  address,
                  `Suppressing "${origin}" kick, last restart was ${Duration.format(Timestamp.delta(lastRestartAt))} ago`
                );
              }
            });
            const { session } = await caseClient.pair(exchange, fabric, peer.address.nodeId, {
              ...options,
              abort: localAbort,
              caseAuthenticatedTags: peer.descriptor.caseAuthenticatedTags,
              maxInitialRetransmissions: Infinity,
              maxInitialRetransmissionTime: timing.maxDelayBetweenInitialContactRetries,
              initialRetransmissionTime: isFallback ? timing.maxDelayBetweenInitialContactRetries : void 0
            });
            outputSession = session;
            overallAbort();
          } catch (_3) {
            var _error3 = _3, _hasError3 = true;
          } finally {
            __callDispose(_stack3, _error3, _hasError3);
          }
        } catch (e) {
          if (AbortedError.is(e)) {
            return;
          }
          throw e;
        } finally {
          kick?.[Symbol.dispose]();
        }
      } catch (_4) {
        var _error4 = _4, _hasError4 = true;
      } finally {
        var _promise = __callDispose(_stack4, _error4, _hasError4);
        _promise && await _promise;
      }
    }
    async function handleConnectionError(e, address, addressAbort, lifetime2) {
      var _stack2 = [];
      try {
        const _handling = __using(_stack2, lifetime2.join("handling error"));
        let delay;
        let category;
        const csre = ChannelStatusResponseError.of(e);
        if (csre) {
          if (csre.generalStatusCode === GeneralStatusCode.Busy && csre.busyDelay !== void 0) {
            delay = Millis(csre.busyDelay + Math.round(Math.random() * timing.delayAfterNetworkError));
            category = "busy";
          } else if (csre.protocolStatusCode === SecureChannelStatusCode.NoSharedTrustRoots && await context.sessions.deleteResumptionRecord(peer.address)) {
            category = "resumption";
          } else {
            delay = timing.delayAfterPeerError;
            category = "peer";
          }
        } else if (causedBy(e, TransientPeerCommunicationError)) {
          delay = timing.delayAfterNetworkError;
          category = "network";
        } else {
          delay = timing.delayAfterUnhandledError;
          category = "general";
        }
        const handleError = options?.handleError ?? context.handleError;
        if (delay !== void 0 && handleError) {
          try {
            const result = handleError(e);
            if (result !== void 0) {
              delay = result;
            }
          } catch (thrown) {
            error(address, "Fatal peer error, aborting connection:", Diagnostic.errorMessage(asError(thrown)));
            fatalError = asError(thrown);
            overallAbort();
            return;
          }
        }
        switch (category) {
          case "busy":
            info(
              via,
              address,
              `Peer requested to delay and retry no earlier than ${Duration.format(csre.busyDelay)} from now (retry in ${Duration.format(delay)})`
            );
            break;
          case "resumption":
            warn(
              address,
              "Authorization rejected by peer on session resumption; clearing resumption data and retrying"
            );
            break;
          case "peer":
            error(address, `Peer error (retry in ${Duration.format(delay)}):`, Diagnostic.errorMessage(e));
            break;
          case "network":
            error(address, `Connection error (retry in ${Duration.format(delay)}):`, Diagnostic.errorMessage(e));
            break;
          case "general":
            error(address, `General connection error (retry in ${Duration.format(delay)}):`, e);
            break;
        }
        if (addressAbort.aborted) {
          return;
        }
        if (delay) {
          await Abort.sleep("peer connection retry", addressAbort, delay);
        }
      } catch (_2) {
        var _error2 = _2, _hasError2 = true;
      } finally {
        __callDispose(_stack2, _error2, _hasError2);
      }
    }
  } catch (_) {
    var _error = _, _hasError = true;
  } finally {
    __callDispose(_stack, _error, _hasError);
  }
}
((PeerConnection2) => {
  function createExchange(peer, exchanges, session, network, protocol = SECURE_CHANNEL_PROTOCOL_ID, addressOverride) {
    return exchanges.initiateExchangeForSession(session, protocol, { onSend, onReceive, network, addressOverride });
    function onSend(_message, retransmission) {
      if (retransmission) {
        peer.service.status.isReachable = false;
      }
    }
    function onReceive() {
      peer.service.status.lastReceiptAt = Time.nowMs;
    }
  }
  PeerConnection2.createExchange = createExchange;
})(PeerConnection || (PeerConnection = {}));
export {
  PeerConnection
};
//# sourceMappingURL=PeerConnection.js.map
