/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  Diagnostic,
  Duration,
  isIpNetworkChannel,
  Logger,
  ServerAddress,
  Time,
  Timestamp
} from "@matter/general";
const logger = Logger.get("PeerAddressMonitor");
class PeerAddressMonitor {
  #peer;
  #abort;
  #timer;
  #cooldownMin;
  #cooldownMax;
  #trackWork;
  #probing;
  // Fibonacci backoff state — resets when probed IP changes
  #lastProbeAt;
  #lastProbedIp;
  #fibPrev = 0;
  #fibCurr = 0;
  constructor(peer, stabilizationDelay, probeCooldown, abort, trackWork) {
    this.#peer = peer;
    this.#abort = abort;
    this.#cooldownMin = probeCooldown.minimum;
    this.#cooldownMax = probeCooldown.maximum;
    this.#trackWork = trackWork;
    this.#timer = Time.getTimer("address check stabilization", stabilizationDelay, () => {
      if (!this.#probing) {
        this.#probing = this.#check().finally(() => {
          this.#probing = void 0;
          this.schedule();
        });
        this.#trackWork(this.#probing);
      }
    });
  }
  /**
   * Schedule a debounced address check.  Restarts the timer on each call so rapid changes coalesce.
   */
  schedule() {
    if (!this.#peer.hasSession) {
      return;
    }
    this.#timer.stop();
    this.#timer.start();
  }
  /**
   * Stop the timer.  Does not cancel an in-flight probe.
   */
  stop() {
    this.#timer.stop();
  }
  async #check() {
    const session = this.#peer.newestSession;
    const interaction = this.#peer.interaction;
    if (!session || !interaction) {
      return;
    }
    const { channel } = session.channel;
    if (!isIpNetworkChannel(channel)) {
      return;
    }
    const currentAddress = channel.networkAddress;
    const currentIp = currentAddress.ip;
    const discoveredAddresses = this.#peer.service.addresses;
    if (!discoveredAddresses.size) {
      return;
    }
    if (discoveredAddresses.has(currentAddress)) {
      return;
    }
    const via = session.via;
    if (currentIp !== this.#lastProbedIp) {
      this.#resetBackoff();
    }
    const lastKnownGood = Timestamp(Math.max(this.#lastProbeAt ?? 0, session.activeTimestamp));
    const cooldown = this.#currentCooldown;
    if (lastKnownGood > 0 && Timestamp.delta(lastKnownGood) < cooldown) {
      return;
    }
    logger.info(
      via,
      "Session address",
      Diagnostic.strong(ServerAddress.urlFor(currentAddress)),
      "no longer in mDNS results, probing"
    );
    this.#lastProbeAt = Time.nowMs;
    this.#lastProbedIp = currentIp;
    const network = this.#peer.network;
    const probeNetwork = network.probeAddress ?? network;
    if (await interaction.probe({ network: probeNetwork.id, abort: this.#abort })) {
      const nextCooldown = this.#advanceBackoff();
      logger.debug(via, `Probe succeeded, keeping session (next cooldown: ${Duration.format(nextCooldown)})`);
      return;
    }
    logger.info(via, "Probe failed, reconnection will use discovered addresses");
    this.#resetBackoff();
  }
  /** Current cooldown duration based on Fibonacci position. */
  get #currentCooldown() {
    return this.#fibCurr || this.#cooldownMin;
  }
  /** Advance the Fibonacci sequence and return the new cooldown. */
  #advanceBackoff() {
    if (this.#fibCurr === 0) {
      this.#fibPrev = this.#cooldownMin;
      this.#fibCurr = this.#cooldownMin;
    } else {
      const next = Math.min(this.#fibPrev + this.#fibCurr, this.#cooldownMax);
      this.#fibPrev = this.#fibCurr;
      this.#fibCurr = next;
    }
    return this.#fibCurr;
  }
  #resetBackoff() {
    this.#fibPrev = 0;
    this.#fibCurr = 0;
    this.#lastProbeAt = void 0;
  }
}
export {
  PeerAddressMonitor
};
//# sourceMappingURL=PeerAddressMonitor.js.map
