/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  Abort,
  CanceledError,
  causedBy,
  Diagnostic,
  Logger,
  MatterAggregateError,
  Millis,
  Seconds
} from "@matter/general";
import { CommissioningError, PeerCommunicationError } from "@matter/protocol";
import { Discovery } from "./Discovery.js";
import { DiscoveryAggregateError, DiscoveryError } from "./DiscoveryError.js";
const logger = Logger.get("ParallelPaseDiscovery");
const CROSS_DEVICE_STAGGER_DELAY = Seconds(1);
class ParallelPaseDiscovery extends Discovery {
  #paseWon = false;
  #pending = /* @__PURE__ */ new Set();
  #abort = new AbortController();
  #winner;
  #winnerAttempt;
  #winnerError;
  #extractWinner;
  #attemptErrors = new Array();
  #attemptCount = 0;
  #startedCount = 0;
  get abortSignal() {
    return this.#abort.signal;
  }
  get paseWon() {
    return this.#paseWon;
  }
  /**
   * Registers a parallel PASE attempt for the given node.
   *
   * @param factory Produces the attempt promise.  Receives a {@code winOnPase} callback that the implementation
   *   passes as the {@code continueXxx} option to the underlying PASE/commissioning call.  When invoked, it
   *   performs the race-gate logic and returns true if this attempt wins, false if another already won.
   * @param extractWinner Extracts the winner value from the settled attempt result.  Called only for the
   *   winning attempt after its promise resolves.
   */
  registerAttempt(factory, extractWinner) {
    let attempt;
    let isWinner = false;
    const winOnPase = () => {
      if (this.#paseWon) {
        return false;
      }
      this.#paseWon = true;
      isWinner = true;
      this.stop();
      this.#abort.abort();
      this.#pending.delete(attempt);
      this.#winnerAttempt = attempt;
      this.#extractWinner = extractWinner;
      return true;
    };
    const attemptIndex = this.#attemptCount++;
    const stagger = Millis(attemptIndex * CROSS_DEVICE_STAGGER_DELAY);
    const startFactory = () => {
      this.#startedCount++;
      return factory(winOnPase);
    };
    attempt = (stagger > 0 ? Abort.sleep("PASE stagger", this.#abort.signal, stagger).then(() => {
      if (!this.#abort.signal.aborted) {
        return startFactory();
      }
    }) : Promise.resolve(startFactory())).catch((error) => {
      if (isWinner) {
        this.#winnerError = error;
        return void 0;
      }
      if (causedBy(error, CanceledError)) {
        logger.debug("Canceled parallel commissioning attempt:", Diagnostic.errorMessage(error));
      } else if (causedBy(error, CommissioningError, PeerCommunicationError)) {
        this.#attemptErrors.push(error);
        logger.debug("Failed parallel commissioning attempt:", Diagnostic.errorMessage(error));
      } else {
        this.#attemptErrors.push(error);
        logger.warn("Unexpected error from parallel commissioning attempt:", error);
      }
      return void 0;
    }).finally(() => {
      this.#pending.delete(attempt);
    });
    this.#pending.add(attempt);
  }
  async onComplete() {
    if (!this.#paseWon) {
      this.#abort.abort();
    }
    try {
      if (this.#winnerAttempt !== void 0) {
        const result = await this.#winnerAttempt;
        if (this.#winnerError !== void 0) {
          throw this.#winnerError;
        }
        this.#winner = this.#extractWinner(result);
      }
    } finally {
      await MatterAggregateError.allSettled([...this.#pending], this.cleanupLabel).catch((error) => {
        logger.warn("Unexpected error during parallel attempt cleanup:", error);
      });
    }
    if (this.#winner === void 0) {
      let detail;
      if (this.#attemptCount === 0) {
        detail = "No commissionable device was discovered";
      } else if (this.#attemptErrors.length > 0) {
        detail = `${this.failureMessage} (${this.#attemptErrors.length} of ${this.#startedCount} started attempt(s) failed, ${this.#attemptCount} discovered)`;
      } else {
        detail = `${this.failureMessage} (${this.#startedCount} attempt(s) started of ${this.#attemptCount} discovered, all canceled or timed out)`;
      }
      const message = `${this} failed: ${detail}`;
      if (this.#attemptErrors.length > 0) {
        throw new DiscoveryAggregateError(this.#attemptErrors, message);
      }
      throw new DiscoveryError(message);
    }
    return this.#winner;
  }
}
export {
  ParallelPaseDiscovery
};
//# sourceMappingURL=ParallelPaseDiscovery.js.map
