/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { AsyncObservable, Diagnostic, ImplementationError, Lifecycle, Logger, Observable } from "@matter/general";
const logger = Logger.get("PartLifecycle");
class EndpointLifecycle {
  #endpoint;
  #isEssential;
  #isInstalled = false;
  #isReady = false;
  #isPartsReady = false;
  #hasId = false;
  #hasNumber = false;
  #installed = Observable((error) => this.emitError("installed", error));
  #ready = Observable((error) => this.emitError("ready", error));
  #partsReady = Observable((error) => this.emitError("partsReady", error));
  #destroying = Observable((error) => this.emitError("destroying", error));
  #destroyed = Observable((error) => this.emitError("destroyed", error));
  #changed = Observable(
    (error) => this.emitError("changed", error)
  );
  #reset = AsyncObservable();
  #queuedUpdates;
  /**
   * Emitted when an endpoint is installed into an initialized owner.
   */
  get installed() {
    return this.#installed;
  }
  /**
   * Emitted when an endpoint is fully initialized excepting children.
   */
  get ready() {
    return this.#ready;
  }
  /**
   * Emitted when an endpoint is fully initialized including children.
   */
  get partsReady() {
    return this.#partsReady;
  }
  /**
   * @deprecated use {@link partsReady}
   */
  get treeReady() {
    return this.partsReady;
  }
  /**
   * Emitted when the endpoint begins destruction.
   */
  get destroying() {
    return this.#destroying;
  }
  /**
   * Emitted when the endpoint is destroyed.
   */
  get destroyed() {
    return this.#destroyed;
  }
  /**
   * Bubbling event indicating changes to endpoint structure.
   */
  get changed() {
    return this.#changed;
  }
  /**
   * Emitted on factory reset.
   */
  get reset() {
    return this.#reset;
  }
  /**
   * Is the {@link Endpoint} installed in a {@link Node}?
   */
  get isInstalled() {
    return this.#isInstalled;
  }
  /**
   * Is the {@link Endpoint} fully initialized, excepting children?
   */
  get isReady() {
    return this.#isReady;
  }
  /**
   * Is the {@link Endpoint} fully initialized, including children?
   */
  get isPartsReady() {
    return this.#isPartsReady;
  }
  /**
   * @deprecated use {@link isPartsReady}
   */
  get isTreeReady() {
    return this.isPartsReady;
  }
  /**
   * Designates endpoint as essential.
   *
   * By default endpoints are considered "essential".  An essential endpoint must initialize successfully or an error
   * is thrown.  Non-essential endpoints may be installed even if they have errors.
   *
   * You may configure this property via {@link Endpoint.Configuration#isEssential}.
   */
  get isEssential() {
    return this.#isEssential;
  }
  /**
   * Does the endpoint have an ID?
   */
  get hasId() {
    return this.#hasId;
  }
  /**
   * Does the endpoint have an endpoint number?
   */
  get hasNumber() {
    return this.#hasNumber;
  }
  constructor(endpoint, isEssential) {
    this.#endpoint = endpoint;
    this.#isEssential = isEssential ?? true;
    endpoint.construction.change.on((status) => {
      if (status === Lifecycle.Status.Crashed) {
        this.change(EndpointLifecycle.Change.Crashed);
      }
    });
  }
  /**
   * Bubble a lifecycle change event from a child.
   */
  bubble(type, endpoint) {
    this.#changed.emit(type, endpoint);
  }
  /**
   * Inform the Lifecycle of a change in lifecycle.
   */
  change(type) {
    switch (type) {
      case EndpointLifecycle.Change.Installed:
        this.#isInstalled = true;
        break;
      case EndpointLifecycle.Change.Destroying:
        this.#isReady = false;
        break;
      case EndpointLifecycle.Change.Ready:
        if (!this.#hasId) {
          throw new ImplementationError("Endpoint reports as ready but has no ID assigned");
        }
        if (!this.#hasNumber) {
          throw new ImplementationError("Endpoint reports as ready but has no number assigned");
        }
        this.#isReady = true;
        break;
      case EndpointLifecycle.Change.PartsReady:
        if (!this.#isReady) {
          throw new ImplementationError("Endpoint reports as parts-ready but is not itself ready");
        }
        this.#isPartsReady = true;
        break;
      case EndpointLifecycle.Change.IdAssigned:
        this.#hasId = true;
        break;
      case EndpointLifecycle.Change.NumberAssigned:
        this.#hasNumber = true;
        break;
    }
    if (this.#queuedUpdates) {
      this.#queuedUpdates?.push(type);
      return;
    }
    this.#queuedUpdates = [type];
    try {
      while (this.#queuedUpdates.length) {
        const type2 = this.#queuedUpdates[0];
        this.#queuedUpdates.shift();
        this.#changed.emit(type2, this.#endpoint);
        const observable = this[type2];
        if (observable !== void 0) {
          observable.emit();
        }
      }
    } finally {
      this.#queuedUpdates = void 0;
    }
  }
  emitError(name, error) {
    logger.error("Unhandled error in", Diagnostic.strong(`${this.#endpoint}.lifecycle.${name}`), "handler:", error);
  }
  /**
   * Revert to uninstalled state.
   */
  resetting() {
    this.#isInstalled = this.#isReady = this.#isPartsReady = false;
  }
}
((EndpointLifecycle2) => {
  let Change;
  ((Change2) => {
    Change2["Installed"] = "installed";
    Change2["Ready"] = "ready";
    Change2["PartsReady"] = "partsReady";
    Change2["Crashed"] = "crashed";
    Change2["Destroying"] = "destroying";
    Change2["Destroyed"] = "destroyed";
    Change2["ServersChanged"] = "serversChanged";
    Change2["ClientsChanged"] = "clientsChanged";
    Change2["IdAssigned"] = "idAssigned";
    Change2["NumberAssigned"] = "numberAssigned";
  })(Change = EndpointLifecycle2.Change || (EndpointLifecycle2.Change = {}));
})(EndpointLifecycle || (EndpointLifecycle = {}));
export {
  EndpointLifecycle
};
//# sourceMappingURL=EndpointLifecycle.js.map
