"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var Endpoint_exports = {};
__export(Endpoint_exports, {
  Endpoint: () => Endpoint
});
module.exports = __toCommonJS(Endpoint_exports);
var import_Behavior = require("#behavior/Behavior.js");
var import_NodeActivity = require("#behavior/context/NodeActivity.js");
var import_OfflineContext = require("#behavior/context/server/OfflineContext.js");
var import_general = require("#general");
var import_model = require("#model");
var import_IdentityService = require("#node/server/IdentityService.js");
var import_types = require("#types");
var import_root = require("../endpoints/root.js");
var import_Agent = require("./Agent.js");
var import_Behaviors = require("./properties/Behaviors.js");
var import_EndpointInitializer = require("./properties/EndpointInitializer.js");
var import_EndpointLifecycle = require("./properties/EndpointLifecycle.js");
var import_Parts = require("./properties/Parts.js");
/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("Endpoint");
class Endpoint {
  #type;
  #id;
  #number;
  #owner;
  #agentType;
  #behaviors;
  #lifecycle;
  #parts;
  #construction;
  #stateView = {};
  #events = {};
  #activity;
  /**
   * A string that uniquely identifies an endpoint.
   *
   * This ID must be unique amongst all Parts with the same owner.
   */
  get id() {
    if (this.#id === void 0) {
      throw new import_general.UninitializedDependencyError(
        this.toString(),
        "endpoint ID is not yet assigned; set ID or await endpoint.construction to avoid this error"
      );
    }
    return this.#id;
  }
  /**
   * The endpoint ID or undefined if not yet assigned.
   */
  get maybeId() {
    return this.#id;
  }
  /**
   * The Matter {@link EndpointNumber} of the endpoint.  This uniquely identifies the {@link Endpoint} in the scope of the
   * Matter node.
   */
  get number() {
    if (this.#number === void 0) {
      throw new import_general.UninitializedDependencyError(
        this.toString(),
        "endpoint number is not yet assigned; set number or await endpoint.construction to avoid this error"
      );
    }
    return this.#number;
  }
  /**
   * The endpoint number or undefined if not yet assigned.
   */
  get maybeNumber() {
    return this.#number;
  }
  /**
   * The owner of the endpoint.
   *
   * Every endpoint but the root endpoint (the "server node") is owned by another endpoint.
   */
  get owner() {
    return this.#owner;
  }
  /**
   * Search for the owner of a specific type.
   *
   * Returns undefined if this owner is not found on the way up to the root endpoint.
   */
  ownerOfType(type) {
    for (let endpoint = this; endpoint !== void 0; endpoint = endpoint.owner) {
      if (endpoint.type.deviceClass === type.deviceClass) {
        return endpoint;
      }
    }
  }
  /**
   * The endpoint's environment.  Endpoint implementations use the environment to access platform components such as
   * storage and network components.
   */
  get env() {
    if (this.owner) {
      return this.owner.env;
    }
    return import_general.Environment.default;
  }
  /**
   * Access the pool of behaviors supported by this endpoint.
   */
  get behaviors() {
    return this.#behaviors;
  }
  /**
   * Current state values for all behaviors, keyed by behavior ID.  This view is read-only.
   */
  get state() {
    return this.#stateView;
  }
  /**
   * Current state for a specific behavior.
   */
  stateOf(type) {
    if (!this.behaviors.has(type)) {
      throw new import_general.ImplementationError(`Behavior ${type.id} is not supported by ${this}`);
    }
    return this.#stateView[type.id];
  }
  /**
   * Update state values.  This is a patch operation; it only modifies properties in {@link values}.
   *
   * {@link values} is an object with a {@link Behavior.id} as the key and state values as sub-objects.
   *
   * Input values must adhere to the {@link Behavior.schema} of the target {@link Behavior}.  If not, set will throw
   * an error.
   *
   * This is a transactional operation.  An error results in no change.  The endpoint will wait for exclusive access
   * before applying changes.
   *
   * @param values the values to change
   */
  async set(values) {
    await this.act(`set<${this}>`, async (agent) => {
      const tx = agent.context.transaction;
      await tx.begin();
      for (const behaviorId in values) {
        const behavior = agent[behaviorId];
        if (!(behavior instanceof import_Behavior.Behavior)) {
          throw new import_general.ImplementationError(`Behavior ID ${behaviorId} does not exist`);
        }
        const vals = values[behaviorId];
        if (vals === void 0) {
          continue;
        }
        await tx.addResources(behavior);
        const patch = behavior.constructor.supervisor.patch;
        if (typeof vals !== "object") {
          throw new import_general.ImplementationError(
            `State values for ${behaviorId} must be an object, not ${typeof vals}`
          );
        }
        if (Array.isArray(vals)) {
          throw new import_general.ImplementationError(`StateValue for ${behaviorId} must be an object, not an array`);
        }
        patch(vals, behavior.state, this.path);
      }
    });
  }
  /**
   * Update state values for a single behavior.
   *
   * The patch semantics used here are identical to {@link set}.
   *
   * @param type the {@link Behavior} to patch
   * @param values the values to change
   */
  async setStateOf(type, values) {
    await this.act(`setStateOf<${this}>`, async (agent) => {
      const behavior = agent.get(type);
      const tx = agent.context.transaction;
      await tx.begin();
      await tx.addResources(behavior);
      const patch = behavior.constructor.supervisor.patch;
      if (typeof values !== "object") {
        throw new import_general.ImplementationError(`State values for ${type.id} must be an object, not ${typeof values}`);
      }
      if (Array.isArray(values)) {
        throw new import_general.ImplementationError(`State values for ${type.id} must be an object, not an array`);
      }
      patch(values, behavior.state, this.path);
    });
  }
  /**
   * Events for all behaviors keyed by behavior ID.
   */
  get events() {
    return this.#events;
  }
  /**
   * Events for a specific behavior.
   */
  eventsOf(type) {
    if (!this.behaviors.has(type)) {
      throw new import_general.ImplementationError(`Behavior ${type.id} is not supported by this endpoint`);
    }
    return this.#events[type.id];
  }
  get construction() {
    return this.#construction;
  }
  constructor(definition, options) {
    const config = Endpoint.configurationFor(definition, options);
    this.#type = config.type;
    this.#construction = (0, import_general.Construction)(this);
    this.#lifecycle = this.createLifecycle(config.isEssential);
    this.#lifecycle.ready.on(() => this.#logReady());
    if (config.id !== void 0) {
      this.id = config.id;
    }
    if (config.number !== void 0) {
      this.number = config.number;
    }
    this.#behaviors = new import_Behaviors.Behaviors(this, config);
    if (config.owner) {
      this.owner = config.owner instanceof import_Agent.Agent ? config.owner.endpoint : config.owner;
    }
    if (config.parts) {
      for (const part of config.parts) {
        this.parts.add(part);
      }
    }
  }
  set id(id) {
    if (this.#id === id) {
      return;
    }
    if (this.#id !== void 0) {
      throw new import_general.ImplementationError(`${this} ID is already assigned, cannot reassign`);
    }
    if (typeof id !== "string") {
      throw new import_general.ImplementationError(`Illegal endpoint ID type "${typeof id}"`);
    }
    if (id === "") {
      throw new import_general.ImplementationError("Endpoint ID may not be empty");
    }
    if (id.includes(".")) {
      throw new import_general.ImplementationError('Endpoint ID may not include "."');
    }
    if (this.lifecycle.isInstalled) {
      this.#container.assertIdAvailable(id, this);
    }
    this.#id = id;
    this.lifecycle.change(import_EndpointLifecycle.EndpointLifecycle.Change.IdAssigned);
  }
  get container() {
    return this.owner?.parts;
  }
  set number(number) {
    if (this.#number === number) {
      return;
    }
    if (this.#number !== void 0) {
      throw new import_general.ImplementationError(
        `${this} endpoint number ${this.#number} is already assigned, cannot reassign`
      );
    }
    if (typeof number !== "number") {
      throw new import_general.ImplementationError(`Illegal endpoint number type "${typeof number}"`);
    }
    if (!Number.isInteger(number)) {
      throw new import_general.ImplementationError(`Endpoint number ${number} is not an integer`);
    }
    if (number < 0) {
      throw new import_general.ImplementationError(`Endpoint number ${number} is negative`);
    }
    if (number > 65535) {
      throw new import_general.ImplementationError(`Endpoint number ${number} is greater than the maximum of 65535`);
    }
    if (this.type.deviceClass === import_root.RootEndpoint.deviceClass) {
      if (number !== 0) {
        throw new import_general.ImplementationError("The root endpoint must have ID 0");
      }
    } else {
      if (number === 0) {
        throw new import_general.ImplementationError("Only root endpoint may have ID 0");
      }
      if (this.lifecycle.isInstalled) {
        this.env.get(import_IdentityService.IdentityService).assertNumberAvailable(number, this);
      }
    }
    this.#number = (0, import_types.EndpointNumber)(number);
    this.lifecycle.change(import_EndpointLifecycle.EndpointLifecycle.Change.NumberAssigned);
  }
  set owner(owner) {
    if (this.#owner === owner) {
      return;
    }
    if (this.#owner) {
      throw new import_general.ImplementationError("Endpoint owner cannot be reassigned");
    }
    if (owner === void 0) {
      throw new import_general.ImplementationError("Endpoint owner must be defined");
    }
    this.#owner = owner;
    try {
      this.#container.add(this);
    } catch (e) {
      this.#container.delete(this);
      this.#owner = void 0;
      throw e;
    }
  }
  async add(definition, options) {
    if (!this.lifecycle.isInstalled) {
      throw new import_general.ImplementationError(`You may not use add() here because ${this} is not installed in a Node`);
    }
    await this.construction;
    let endpoint;
    if (definition instanceof Endpoint) {
      endpoint = definition;
    } else {
      endpoint = new Endpoint(definition, options);
    }
    if (this.#lifecycle.isPartsReady) {
      endpoint.construction.onError(() => {
      });
    }
    this.parts.add(endpoint);
    try {
      await endpoint.construction.ready;
    } catch (e) {
      if (endpoint.lifecycle.isEssential) {
        await endpoint.reset();
        this.parts.delete(endpoint);
        endpoint.#owner = void 0;
      }
      if (!endpoint.lifecycle.isEssential) {
        logger.error(`Initialization error in non-essential endpoint ${endpoint}:`, e);
      }
      throw e;
    }
    return endpoint;
  }
  /**
   * The type of endpoint this endpoint implements.
   */
  get type() {
    return this.#type;
  }
  /**
   * Access child parts.
   */
  get parts() {
    if (!this.#parts) {
      this.#parts = new import_Parts.Parts(this);
    }
    return this.#parts;
  }
  /**
   * Is this a parent Endpoint?
   */
  get hasParts() {
    return !!this.#parts?.size;
  }
  /**
   * Endpoint information that varies as the endpoint initializes.
   */
  get lifecycle() {
    return this.#lifecycle;
  }
  createLifecycle(isEssential) {
    return new import_EndpointLifecycle.EndpointLifecycle(this, isEssential);
  }
  /**
   * Create an {@link Agent.Type} for the endpoint.
   */
  get agentType() {
    if (!this.#agentType) {
      this.#agentType = import_Agent.Agent.for(this.type, this.behaviors.supported);
    }
    return this.#agentType;
  }
  act(actorOrPurpose, actor) {
    let purpose;
    if (typeof actorOrPurpose === "string") {
      purpose = actorOrPurpose;
    } else {
      actor = actorOrPurpose;
      purpose = "offline";
    }
    if (typeof actor !== "function") {
      throw new import_general.ImplementationError("Actor must be a function");
    }
    this.construction.assert(this.toString());
    if (!this.#activity) {
      this.#activity = this.env.get(import_NodeActivity.NodeActivity);
    }
    return import_OfflineContext.OfflineContext.act(purpose, this.#activity, (context) => {
      return actor(context.agentFor(this));
    });
  }
  /**
   * Perform "soft" reset of the endpoint, reverting all in-memory structures to uninitialized.
   */
  async reset() {
    try {
      this.lifecycle.resetting();
      await this.parts.reset();
      await this.behaviors.close();
      await this.lifecycle.reset.emit();
      this.construction.setStatus(import_general.Lifecycle.Status.Inactive);
    } catch (e) {
      logger.error(`Unhandled error during reset of ${this}`, e);
    }
  }
  /**
   * Apply a depth-first visitor function to myself and all descendents.
   */
  visit(visitor) {
    const promise = visitor(this);
    const childIterator = this.parts[Symbol.iterator]();
    const visitChildren = () => {
      for (let next = childIterator.next(); !next.done; next = childIterator.next()) {
        const promise2 = next.value.visit(visitor);
        if (import_general.MaybePromise.is(promise2)) {
          return promise2.then(visitChildren);
        }
      }
    };
    if (import_general.MaybePromise.is(promise)) {
      return promise.then(visitChildren);
    }
    return visitChildren();
  }
  async close() {
    await this.#construction.close();
  }
  async [import_general.Construction.destruct]() {
    await this.#parts?.close();
    await this.#behaviors?.close();
    for (const events of Object.values(this.#events)) {
      events[Symbol.dispose]();
    }
    this.lifecycle.change(import_EndpointLifecycle.EndpointLifecycle.Change.Destroyed);
    this.#owner = void 0;
  }
  async [Symbol.asyncDispose]() {
    await this.close();
  }
  toString() {
    return this.path.toString();
  }
  /**
   * Path identifying the endpoint in the Matter data model.
   */
  get path() {
    let ident;
    if (this.lifecycle?.hasId) {
      ident = this.id;
    } else if (this.lifecycle?.hasNumber) {
      ident = this.number;
    } else {
      ident = "?";
    }
    if (this.#owner) {
      return this.#owner.path.at(ident, this.#type.name);
    }
    return (0, import_model.DataModelPath)(ident, this.type?.name);
  }
  /**
   * Asynchronous initialization.
   *
   * Derivatives may override to perform async construction prior to full initialization.
   */
  initialize() {
    this.env.get(import_EndpointInitializer.EndpointInitializer).initializeDescendent(this);
    let promise = this.behaviors.initialize();
    if (promise) {
      promise = promise.then(this.parts.initialize.bind(this.parts));
    } else {
      promise = this.parts.initialize();
    }
    return promise;
  }
  /**
   * Ensure requirements for construction are met.
   */
  assertConstructable() {
    if (this.#owner === void 0) {
      throw new import_general.ImplementationError(`Endpoint construction initiated without owner`);
    }
    if (!this.#owner.#lifecycle.isInstalled) {
      throw new import_general.ImplementationError(`Endpoint construction initiated with uninstalled owner`);
    }
  }
  /**
   * Complete initialization.  Invoked via {@link Construction#start} by the owner.
   */
  [import_general.Construction.construct]() {
    this.assertConstructable();
    this.lifecycle.change(import_EndpointLifecycle.EndpointLifecycle.Change.Installed);
    return this.initialize();
  }
  #logReady() {
    logger.info(import_general.Diagnostic.strong(this.toString()), "ready", this.diagnosticDict);
  }
  /**
   * Hierarchical diagnostics of endpoint and children.
   */
  get [import_general.Diagnostic.value]() {
    return [
      import_general.Diagnostic.strong(this.id),
      import_general.Diagnostic.dict({
        ...this.#diagnosticProps,
        class: this.constructor.name
      }),
      import_general.Diagnostic.list([...this.behaviors.detailedDiagnostic, ...this.parts])
    ];
  }
  /**
   * Diagnostic information regarding endpoint state.
   */
  get diagnosticDict() {
    return import_general.Diagnostic.dict({
      ...this.#diagnosticProps,
      behaviors: this.behaviors
    });
  }
  get #diagnosticProps() {
    return {
      "endpoint#": this.number,
      type: `${this.type.name} (0x${this.type.deviceType.toString(16)})`
    };
  }
  get #container() {
    const container = this.container;
    if (container === void 0) {
      throw new import_general.ImplementationError(`No container for installed endpoint ${this}`);
    }
    return container;
  }
}
((Endpoint2) => {
  function configurationFor(definition, options) {
    if (definition.deviceType) {
      return {
        ...options,
        type: definition
      };
    }
    return definition;
  }
  Endpoint2.configurationFor = configurationFor;
  function partFor(definition) {
    if (definition instanceof Endpoint2) {
      return definition;
    }
    return new Endpoint2(definition);
  }
  Endpoint2.partFor = partFor;
})(Endpoint || (Endpoint = {}));
//# sourceMappingURL=Endpoint.js.map
