var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
var __using = (stack, value, async) => {
  if (value != null) {
    if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
    var dispose, inner;
    if (async) dispose = value[__knownSymbol("asyncDispose")];
    if (dispose === void 0) {
      dispose = value[__knownSymbol("dispose")];
      if (async) inner = dispose;
    }
    if (typeof dispose !== "function") __typeError("Object not disposable");
    if (inner) dispose = function() {
      try {
        inner.call(this);
      } catch (e) {
        return Promise.reject(e);
      }
    };
    stack.push([async, dispose, value]);
  } else if (async) {
    stack.push([async]);
  }
  return value;
};
var __callDispose = (stack, error, hasError) => {
  var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
    return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
  };
  var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
  var next = (it) => {
    while (it = stack.pop()) {
      try {
        var result = it[1] && it[1].call(it[2]);
        if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
      } catch (e) {
        fail(e);
      }
    }
    if (hasError) throw error;
  };
  return next();
};
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Behavior } from "#behavior/Behavior.js";
import { NodeActivity } from "#behavior/context/NodeActivity.js";
import { ContextAgents } from "#behavior/context/server/ContextAgents.js";
import { LocalActorContext } from "#behavior/context/server/LocalActorContext.js";
import { ProtocolService } from "#node/integration/ProtocolService.js";
import { IdentityService } from "#node/server/IdentityService.js";
import {
  Construction,
  Diagnostic,
  Environment,
  ImplementationError,
  Lifecycle,
  Lifetime,
  Logger,
  MaybePromise,
  toHex,
  UninitializedDependencyError
} from "@matter/general";
import { DataModelPath } from "@matter/model";
import { EndpointNumber } from "@matter/types";
import { RootEndpoint } from "../endpoints/root.js";
import { Agent } from "./Agent.js";
import { Behaviors } from "./properties/Behaviors.js";
import { Commands } from "./properties/Commands.js";
import { EndpointInitializer } from "./properties/EndpointInitializer.js";
import { EndpointLifecycle } from "./properties/EndpointLifecycle.js";
import { Parts } from "./properties/Parts.js";
import { EndpointType } from "./type/EndpointType.js";
const logger = Logger.get("Endpoint");
class Endpoint {
  #type;
  #id;
  #number;
  #owner;
  #agentType;
  #behaviors;
  #lifecycle;
  #parts;
  #construction;
  #stateView = {};
  #events = {};
  #commands;
  #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 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 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;
  }
  /**
   * Access an {@link Agent} for this endpoint.
   *
   * An {@link Agent} allows you to interact directly with the behaviors supported by the endpoint.  Normally you
   * would use {@link act} to obtain an agent but {@link agentFor} is useful if you need to interact with multiple
   * endpoints in the same context.
   */
  agentFor(context) {
    return ContextAgents(context).agentFor(this);
  }
  get endpointProtocol() {
    if (this.#number === void 0 || !this.env.has(ProtocolService)) {
      return void 0;
    }
    return this.env.get(ProtocolService).protocol[this.#number];
  }
  /**
   * 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.deviceType === type.deviceType) {
        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 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;
  }
  stateOf(type) {
    const state = this.maybeStateOf(type);
    if (state) {
      return state;
    }
    const id = typeof type === "string" ? type : type.id;
    throw new ImplementationError(`Behavior ${id} is not supported by ${this}`);
  }
  maybeStateOf(type) {
    if (typeof type === "string") {
      if (!(type in this.#stateView)) {
        return void 0;
      }
    } else {
      if (!this.behaviors.has(type)) {
        return void 0;
      }
      type = type.id;
    }
    return this.#stateView[type];
  }
  /**
   * 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 Behavior)) {
          throw new ImplementationError(`Behavior ID ${behaviorId} does not exist`);
        }
        const vals = values[behaviorId];
        if (vals === void 0) {
          continue;
        }
        await tx.addResources(behavior);
        const patch = behavior.type.supervisor.patch;
        if (typeof vals !== "object") {
          throw new ImplementationError(
            `State values for ${behaviorId} must be an object, not ${typeof vals}`
          );
        }
        if (vals instanceof Date) {
          throw new ImplementationError(
            `State values for ${behaviorId} must be an object, not ${vals.constructor.name}`
          );
        }
        if (Array.isArray(vals)) {
          throw new ImplementationError(`State value for ${behaviorId} must be an object, not an array`);
        }
        patch(vals, behavior.state, this.path);
      }
    });
  }
  async setStateOf(type, values) {
    if (typeof type === "string") {
      const typeName = type;
      type = this.behaviors.supported[type];
      if (type === void 0) {
        throw new ImplementationError(`Behavior ${typeName} is not supported by ${this}`);
      }
    }
    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.type.supervisor.patch;
      if (typeof values !== "object") {
        throw new ImplementationError(`State values for ${type.id} must be an object, not ${typeof values}`);
      }
      if (values instanceof Date) {
        throw new ImplementationError(
          `State values for ${type.id} must be an object, not ${values.constructor.name}`
        );
      }
      if (Array.isArray(values)) {
        throw new ImplementationError(`State values for ${type.id} must be an object, not an array`);
      }
      patch(values, behavior.state, this.path);
    });
  }
  /**
   * Commands for all behaviors keyed by behavior ID.
   */
  get commands() {
    if (this.#commands === void 0) {
      this.#commands = Commands(this);
    }
    return this.#commands;
  }
  /**
   * Commands for a specific behavior.
   */
  commandsOf(type) {
    if (!this.behaviors.has(type)) {
      throw new ImplementationError(`Behavior ${type.id} is not supported by this endpoint`);
    }
    return this.commands[type.id];
  }
  /**
   * Events for all behaviors keyed by behavior ID.
   */
  get events() {
    return this.#events;
  }
  eventsOf(type) {
    if (typeof type === "string") {
      if (!(type in this.#stateView)) {
        throw new ImplementationError(`Behavior ${type} is not supported by ${this}`);
      }
    } else {
      if (!this.behaviors.has(type)) {
        throw new ImplementationError(`Behavior ${type.id} is not supported by ${this}`);
      }
      type = type.id;
    }
    return this.#events[type];
  }
  get construction() {
    return this.#construction;
  }
  constructor(definition, options) {
    const config = Endpoint.configurationFor(definition, options);
    this.#type = config.type;
    this.#construction = 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 Behaviors(this, config);
    if (config.owner) {
      this.owner = config.owner instanceof 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 ImplementationError(`${this} ID is already assigned, cannot reassign`);
    }
    if (typeof id !== "string") {
      throw new ImplementationError(`Illegal endpoint ID type "${typeof id}"`);
    }
    if (id === "") {
      throw new ImplementationError("Endpoint ID may not be empty");
    }
    if (id.includes(".")) {
      throw new ImplementationError('Endpoint ID may not include "."');
    }
    if (this.lifecycle.isInstalled) {
      this.#container.assertIdAvailable(id, this);
    }
    this.#id = id;
    this.lifecycle.change(EndpointLifecycle.Change.IdAssigned);
  }
  get container() {
    return this.owner?.parts;
  }
  set number(number) {
    if (this.#number === number) {
      return;
    }
    if (this.#number !== void 0) {
      throw new ImplementationError(
        `${this} endpoint number ${this.#number} is already assigned, cannot reassign`
      );
    }
    if (typeof number !== "number") {
      throw new ImplementationError(`Illegal endpoint number type "${typeof number}"`);
    }
    if (!Number.isInteger(number)) {
      throw new ImplementationError(`Endpoint number ${number} is not an integer`);
    }
    if (number < 0) {
      throw new ImplementationError(`Endpoint number ${number} is negative`);
    }
    if (number > 65535) {
      throw new ImplementationError(`Endpoint number ${number} is greater than the maximum of 65535`);
    }
    if (this.type.deviceClass === RootEndpoint.deviceClass) {
      if (number !== 0) {
        throw new ImplementationError("The root endpoint must have ID 0");
      }
    } else {
      if (number === 0) {
        throw new ImplementationError("Only root endpoint may have ID 0");
      }
      if (this.lifecycle.isInstalled) {
        this.env.get(IdentityService).assertEndpointNumberAvailable(number, this);
      }
    }
    this.#number = EndpointNumber(number);
    this.lifecycle.change(EndpointLifecycle.Change.NumberAssigned);
  }
  set owner(owner) {
    if (this.#owner === owner) {
      return;
    }
    if (this.#owner) {
      this.#container.delete(this);
    }
    this.#owner = owner;
    if (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 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 Parts(this);
    }
    return this.#parts;
  }
  /**
   * Is this a parent Endpoint?
   */
  get hasParts() {
    if (this.#parts?.size) {
      return true;
    }
    return false;
  }
  /**
   * Endpoint information that varies as the endpoint initializes.
   */
  get lifecycle() {
    return this.#lifecycle;
  }
  createLifecycle(isEssential) {
    return new EndpointLifecycle(this, isEssential);
  }
  /**
   * Create an {@link Agent.Type} for the endpoint.
   */
  get agentType() {
    if (!this.#agentType) {
      this.#agentType = 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 ImplementationError("Actor must be a function");
    }
    this.construction.assert(this.toString());
    if (!this.#activity) {
      this.#activity = this.env.get(NodeActivity);
    }
    return LocalActorContext.act(
      purpose,
      (context) => {
        return actor(this.agentFor(context));
      },
      { activity: this.#activity, lifetime: this.construction }
    );
  }
  /**
   * Perform "soft" reset of the endpoint, reverting all in-memory structures to uninitialized.
   */
  async reset() {
    var _stack4 = [];
    try {
      const _resetting = __using(_stack4, this.construction.join("resetting"));
      try {
        this.lifecycle.resetting();
        {
          var _stack = [];
          try {
            const _parts = __using(_stack, _resetting.join("parts"));
            await this.parts.reset();
          } catch (_) {
            var _error = _, _hasError = true;
          } finally {
            __callDispose(_stack, _error, _hasError);
          }
        }
        {
          var _stack2 = [];
          try {
            const _behaviors = __using(_stack2, _resetting.join("behaviors"));
            await this.behaviors.close();
          } catch (_2) {
            var _error2 = _2, _hasError2 = true;
          } finally {
            __callDispose(_stack2, _error2, _hasError2);
          }
        }
        {
          var _stack3 = [];
          try {
            const _lifecycle = __using(_stack3, _resetting.join("lifecycle"));
            await this.lifecycle.reset.emit();
          } catch (_3) {
            var _error3 = _3, _hasError3 = true;
          } finally {
            __callDispose(_stack3, _error3, _hasError3);
          }
        }
        this.construction.setStatus(Lifecycle.Status.Inactive);
      } catch (e) {
        logger.error(`Unhandled error during reset of ${this}`, e);
      }
    } catch (_4) {
      var _error4 = _4, _hasError4 = true;
    } finally {
      __callDispose(_stack4, _error4, _hasError4);
    }
  }
  /**
   * Perform "hard" reset of the endpoint, reverting all in-memory and persistent state to uninitialized.
   */
  async erase() {
    await this.reset();
    await this.env.get(EndpointInitializer).eraseDescendant(this);
  }
  /**
   * Erase all persisted data and destroy the endpoint.
   */
  async delete() {
    this.lifecycle.change(EndpointLifecycle.Change.Destroying);
    await this.erase();
    await this.close();
  }
  /**
   * 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 (MaybePromise.is(promise2)) {
          return promise2.then(visitChildren);
        }
      }
    };
    if (MaybePromise.is(promise)) {
      return promise.then(visitChildren);
    }
    return visitChildren();
  }
  async close() {
    this.lifecycle.change(EndpointLifecycle.Change.Destroying);
    await this.env.get(EndpointInitializer).deactivateDescendant(this);
    await this.#construction.close();
  }
  async [Construction.destruct]() {
    await this.#parts?.close();
    await this.#behaviors?.close();
    for (const id in this.#events) {
      this.#events[id][Symbol.dispose]();
    }
    this.lifecycle.change(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() {
    if (this.#owner) {
      return this.#owner.path.at(this.identity, this.#type.name);
    }
    return DataModelPath(this.identity, this.type?.name);
  }
  /**
   * Diagnostic identity.
   *
   * This is an unqualified path segment.
   */
  get identity() {
    if (this.lifecycle?.hasId) {
      return this.id;
    }
    if (this.lifecycle?.hasNumber) {
      return this.number;
    }
    return "?";
  }
  /**
   * Asynchronous initialization.
   *
   * Derivatives may override to perform async construction prior to full initialization.
   */
  initialize() {
    this.env.get(EndpointInitializer).initializeDescendant(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 ImplementationError(`Endpoint construction initiated without owner`);
    }
    if (!this.#owner.#lifecycle.isInstalled) {
      throw new ImplementationError(`Endpoint construction initiated with uninstalled owner`);
    }
  }
  /**
   * Complete initialization.  Invoked via {@link Construction#start} by the owner.
   */
  [Construction.construct]() {
    this.assertConstructable();
    this.lifecycle.change(EndpointLifecycle.Change.Installed);
    return this.initialize();
  }
  #logReady() {
    logger.info(Diagnostic.strong(this.toString()), "ready", this.diagnosticDict);
  }
  /**
   * Hierarchical diagnostics of endpoint and children.
   */
  get [Diagnostic.value]() {
    const diagnostics = [
      Diagnostic.strong(this.#id === void 0 ? "(no id)" : this.id),
      Diagnostic.dict({
        ...this.#diagnosticProps,
        class: this.constructor.name
      })
    ];
    if (this.#construction.status === Lifecycle.Status.Active) {
      diagnostics.push(Diagnostic.list([...this.behaviors.detailedDiagnostic, ...this.parts]));
    } else {
      diagnostics.push(Diagnostic.weak(`(${this.#construction.status})`));
    }
    return diagnostics;
  }
  get [Lifetime.owner]() {
    return this.#owner?.construction;
  }
  /**
   * Diagnostic information regarding endpoint state.
   */
  get diagnosticDict() {
    return Diagnostic.dict({
      ...this.#diagnosticProps,
      behaviors: this.behaviors
    });
  }
  get #diagnosticProps() {
    const type = this.type;
    return {
      "endpoint#": this.#number ? this.number : "(unassigned)",
      type: `${type.name} (${type.deviceType === EndpointType.UNKNOWN_DEVICE_TYPE ? "unknown" : `0x${toHex(type.deviceType)}${type.deviceRevision === EndpointType.UNKNOWN_DEVICE_REVISION ? "" : `, rev ${type.deviceRevision}`}`})`
    };
  }
  get #container() {
    const container = this.container;
    if (container === void 0) {
      throw new 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 = {}));
export {
  Endpoint
};
//# sourceMappingURL=Endpoint.js.map
