/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Construction, ImplementationError, Lifecycle, Logger } from "#general";
import { IdentityConflictError, IdentityService } from "#node/server/IdentityService.js";
import { Agent } from "../Agent.js";
import { Endpoint } from "../Endpoint.js";
import { EndpointPartsError, PartNotFoundError } from "../errors.js";
import { EndpointContainer } from "./EndpointContainer.js";
import { EndpointLifecycle } from "./EndpointLifecycle.js";
const logger = Logger.get("Parts");
class Parts extends EndpointContainer {
  #bubbleChange;
  constructor(endpoint) {
    super(endpoint);
    const lifecycle = this.endpoint.lifecycle;
    this.#bubbleChange = (type, endpoint2) => lifecycle.bubble(type, endpoint2);
  }
  add(child) {
    const endpoint = this.#endpointFor(child);
    if (this.has(endpoint)) {
      return;
    }
    if (this.endpoint.lifecycle.isReady) {
      this.#validateInsertion(endpoint, endpoint);
    }
    super.add(endpoint);
    endpoint.lifecycle.changed.on((type, endpoint2) => this.#bubbleChange(type, endpoint2));
    if (this.endpoint.lifecycle.isPartsReady) {
      if (!endpoint.construction.isErrorHandled) {
        endpoint.construction.onError((error) => logger.error(`Error initializing ${endpoint}:`, error));
      }
      endpoint.construction.start();
    }
    return endpoint;
  }
  delete(child) {
    const endpoint = this.#endpointFor(child);
    if (!super.delete(this.#endpointFor(child))) {
      return false;
    }
    const childLifeCycle = endpoint.lifecycle;
    childLifeCycle.changed.off(this.#bubbleChange);
    return true;
  }
  require(id) {
    const part = this.get(id);
    if (part === void 0) {
      throw new PartNotFoundError(`Endpoint ${this.endpoint} has no part ${id}`);
    }
    return part;
  }
  has(identity) {
    if (typeof identity === "string") {
      for (const child of this) {
        if (child.maybeId === identity) {
          return true;
        }
      }
      return false;
    }
    return super.has(this.#endpointFor(identity));
  }
  indexOf(child) {
    const endpoint = this.#endpointFor(child);
    let index = 0;
    for (const other of this) {
      if (endpoint === other) {
        return index;
      }
      index++;
    }
    return -1;
  }
  /**
   * Initialize all uninitialized Parts.
   *
   * Invoked automatically by the owner after behaviors initialize.
   */
  initialize() {
    if (!this.endpoint.lifecycle.isReady) {
      throw new ImplementationError(`Cannot initialize parts because endpoint is not ready`);
    }
    const onPartsReady = () => this.endpoint.lifecycle.change(EndpointLifecycle.Change.PartsReady);
    if (!this.size) {
      onPartsReady();
      return;
    }
    for (const endpoint of this) {
      const isInactive = endpoint.construction.status === Lifecycle.Status.Inactive;
      if (isInactive || endpoint.construction.status === Lifecycle.Status.Initializing) {
        endpoint.construction.onError((error) => {
          logger.error(`Error initializing ${endpoint}:`, error);
        });
        if (isInactive) {
          endpoint.construction.start();
        }
      }
    }
    const onPartsCrashed = (errored) => {
      const essential = [...errored].filter((endpoint) => endpoint.lifecycle.isEssential);
      if (essential.length) {
        return new EndpointPartsError(essential);
      }
    };
    let promise = Construction.all(this, onPartsCrashed);
    if (promise) {
      promise = promise.then(onPartsReady);
    } else {
      onPartsReady();
    }
    return promise;
  }
  #validateInsertion(forefather, endpoint, usedNumbers) {
    if (endpoint.lifecycle.hasNumber) {
      this.endpoint.env.get(IdentityService).assertNumberAvailable(endpoint.number, endpoint);
      if (usedNumbers?.has(endpoint.number)) {
        throw new IdentityConflictError(
          `Cannot add endpoint ${forefather} because descendents have conflicting definitions for endpoint number ${endpoint.number}`
        );
      }
    }
    if (!endpoint.hasParts) {
      return;
    }
    const children = endpoint.parts;
    if (!children.size) {
      return;
    }
    if (!usedNumbers) {
      usedNumbers = /* @__PURE__ */ new Set();
    }
    if (endpoint.lifecycle.hasNumber) {
      usedNumbers.add(endpoint.number);
    }
    for (const child of children) {
      this.#validateInsertion(forefather, child, usedNumbers);
    }
  }
  #endpointFor(child) {
    if (child instanceof Agent) {
      child = child.endpoint;
    }
    if (!(child instanceof Endpoint)) {
      if (child.type) {
        child.owner = this.endpoint;
      } else {
        child = {
          type: child,
          owner: this.endpoint
        };
      }
    }
    return Endpoint.partFor(child);
  }
}
export {
  Parts
};
//# sourceMappingURL=Parts.js.map
