/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Diagnostic } from "#log/Diagnostic.js";
import { Logger } from "#log/Logger.js";
import { AbortedError } from "#MatterError.js";
import { ServerAddress } from "#net/ServerAddress.js";
import { Time } from "#time/Time.js";
import { Abort } from "#util/Abort.js";
import { asError } from "#util/Error.js";
import { BasicSet } from "#util/Set.js";
import { IpServiceResolution } from "./IpServiceResolution.js";
const logger = Logger.get("IpServiceStatus");
class IpServiceStatus {
  #service;
  #isReachable = false;
  #connecting = new BasicSet();
  #resolveAbort;
  #resolving;
  #connectionInitiatedAt;
  #lastReceiptAt;
  constructor(service) {
    this.#service = service;
  }
  async close() {
    this.#stopResolving();
    if (this.#resolving) {
      await this.#resolving;
    }
  }
  /**
   * Is the service actively connecting?
   *
   * This is true so long as a promise passed to {@link connecting} is unresolved.
   */
  get isConnecting() {
    return this.#connecting.size > 0;
  }
  /**
   * Is the service currently reachable?
   *
   * This value is writable.  If you set {@link isReachable} to false and {@link isConnecting} is true, the service
   * enters discovery mode and begins active solicitation so long as neither condition changes.
   *
   * The service sets {@link isReachable} to true automatically if:
   *
   * - It discovers a new (previously unknown) address, or
   *
   * - The input promise to {@link connecting} resolves to true
   *
   * The service sets {@link isReachable} to false automatically if the input promise to {@link connecting} resolves
   * to false.
   */
  get isReachable() {
    return this.#isReachable;
  }
  /**
   * Are we actively performing MDNS discovery for the service?
   */
  get isResolving() {
    return this.#resolving !== void 0;
  }
  set isReachable(isReachable) {
    if (this.#isReachable === isReachable) {
      return;
    }
    this.#isReachable = isReachable;
    if (isReachable) {
      this.#maybeStopResolving();
    } else {
      this.#maybeStartResolving();
    }
  }
  get lastReceiptAt() {
    return this.#lastReceiptAt;
  }
  set lastReceiptAt(time) {
    this.#lastReceiptAt = time;
  }
  get connectionInitiatedAt() {
    return this.#connectionInitiatedAt;
  }
  /**
   * Register a new connection attempt.
   *
   * If {@link result} resolves as true the service is marked as reachable.  If {@link result} resolves as false
   * reachability is not modified.
   *
   * If {@link result} throws an error other than {@link AbortedError}, the service is marked as unreachable and if
   * the error logged.
   *
   * {@link isConnecting} will be true until {@link result} resolves.
   */
  connecting(result) {
    logger.debug(this.#service.via, "Connecting");
    result.then(
      (returned) => {
        this.#connecting.delete(result);
        if (!this.#connecting.size) {
          this.#connectionInitiatedAt = void 0;
        }
        if (returned) {
          this.isReachable = true;
          logger.info(this.#service.via, "Connected");
        } else {
          logger.debug(this.#service.via, "Connect attempt aborted");
        }
        this.#maybeStopResolving();
      },
      (error) => {
        this.#connecting.delete(result);
        if (!(error instanceof AbortedError)) {
          return;
        }
        logger.error(this.#service.via, "Connection error:", asError(error));
        this.#isReachable = false;
        this.#maybeStartResolving();
      }
    );
    this.#connectionInitiatedAt = Time.nowMs;
    this.#connecting.add(result);
    this.#maybeStartResolving();
  }
  #maybeStartResolving() {
    if (this.#isReachable || !this.isConnecting || this.#resolveAbort) {
      return;
    }
    const numAddresses = this.#service.addresses.size;
    let why;
    switch (numAddresses) {
      case 0:
        why = "need address";
        break;
      case 1:
        why = "address is unreachable";
        break;
      default:
        why = `${numAddresses} known addresses are unreachable`;
    }
    logger.info(this.#service.via, "Resolving", Diagnostic.weak(`(${why})`));
    this.#resolveAbort = new Abort();
    this.#resolving = IpServiceResolution(this.#service, this.#resolveAbort).finally(() => {
      if (this.#resolveAbort?.aborted === false) {
        const addresses = [...this.#service.addresses].map(ServerAddress.urlFor);
        logger.debug(this.#service.via, `Resolved as ${addresses.join(", ")}`);
      }
      this.#resolveAbort?.close();
      this.#resolveAbort = void 0;
      this.#resolving = void 0;
    });
  }
  #maybeStopResolving() {
    if (!this.#isReachable || this.isConnecting || !this.#resolveAbort) {
      return;
    }
    this.#stopResolving();
  }
  #stopResolving() {
    if (!this.#resolveAbort) {
      return;
    }
    this.#resolveAbort();
    logger.debug(this.#service.via, "Stopped resolving");
  }
}
export {
  IpServiceStatus
};
//# sourceMappingURL=IpServiceStatus.js.map
