"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 Behaviors_exports = {};
__export(Behaviors_exports, {
  Behaviors: () => Behaviors
});
module.exports = __toCommonJS(Behaviors_exports);
var import_Behavior = require("#behavior/Behavior.js");
var import_NodeActivity = require("#behavior/context/NodeActivity.js");
var import_LocalActorContext = require("#behavior/context/server/LocalActorContext.js");
var import_general = require("#general");
var import_model = require("#model");
var import_ProtocolService = require("#node/integration/ProtocolService.js");
var import_types = require("#types");
var import_EndpointVariableService = require("../EndpointVariableService.js");
var import_errors = require("../errors.js");
var import_EndpointInitializer = require("./EndpointInitializer.js");
var import_EndpointLifecycle = require("./EndpointLifecycle.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("Behaviors");
class Behaviors {
  #endpoint;
  #supported;
  #backings = {};
  #events = {};
  #options;
  #protocol;
  #detachedObservers;
  /**
   * The {@link SupportedBehaviors} of the {@link Endpoint}.
   */
  get supported() {
    return this.#supported;
  }
  /**
   * Obtain the specific {@link Behavior.Type} used by the endpoint for implementation if the endpoint supports
   * {@link type}.
   */
  typeFor(type) {
    const supported = this.#supported[type.id];
    if (!supported) {
      return;
    }
    if (!supported.supports(type)) {
      return void 0;
    }
    return supported;
  }
  /**
   * The list of active behaviors.
   */
  get active() {
    return Object.values(this.#backings).map((backing) => backing.type);
  }
  get status() {
    const status = {};
    for (const key in this.#supported) {
      status[key] = this.#backings[key]?.status ?? import_general.Lifecycle.Status.Inactive;
    }
    return status;
  }
  get [import_general.Diagnostic.value]() {
    return import_general.Diagnostic.lifecycleList(this.status);
  }
  get detailedDiagnostic() {
    return Object.entries(this.#supported).map(([name, type]) => {
      const backing = this.#backings[name];
      const cluster = clusterOf(type);
      const result = [
        (0, import_general.Diagnostic)(backing?.status ?? import_general.Lifecycle.Status.Inactive, name),
        import_general.Diagnostic.dict({
          id: cluster ? import_general.Diagnostic.hex(cluster.id) : void 0
        })
      ];
      if (!cluster) {
        return result;
      }
      const elements = this.elementsOf(type);
      const elementDiagnostic = Array();
      const features = new import_model.FeatureSet(cluster.supportedFeatures);
      if (features.size) {
        elementDiagnostic.push([import_general.Diagnostic.strong("features"), features]);
      }
      if (elements.attributes.size) {
        const behaviorData = new Array();
        for (const attributeName of elements.attributes) {
          const attr = cluster.attributes[attributeName];
          if (attr) {
            behaviorData.push([
              attributeName,
              import_general.Diagnostic.dict({
                id: import_general.Diagnostic.hex(attr.id),
                val: backing?.stateView?.[attributeName],
                flags: import_general.Diagnostic.asFlags({
                  fabricScoped: attr.fabricScoped
                })
              })
            ]);
          }
        }
        elementDiagnostic.push([import_general.Diagnostic.strong("attributes"), import_general.Diagnostic.list(behaviorData)]);
      }
      if (elements.commands.size) {
        elementDiagnostic.push([
          import_general.Diagnostic.strong("commands"),
          import_general.Diagnostic.list(
            [...elements.commands].map((name2) => [
              name2,
              import_general.Diagnostic.weak(
                `(${import_general.Diagnostic.hex(cluster.commands[name2].requestId)}${cluster.commands[name2].responseSchema instanceof import_types.VoidSchema ? "" : `/${import_general.Diagnostic.hex(cluster.commands[name2].responseId)}`})`
              )
            ])
          )
        ]);
      }
      if (elements.events.size) {
        elementDiagnostic.push(
          import_general.Diagnostic.strong("events"),
          import_general.Diagnostic.list([
            [...elements.events].map((name2) => [
              name2,
              import_general.Diagnostic.weak(`(${import_general.Diagnostic.hex(cluster.events[name2].id)})`)
            ])
          ])
        );
      }
      if (elementDiagnostic.length) {
        result.push(import_general.Diagnostic.list(elementDiagnostic));
      }
      return result;
    });
  }
  constructor(endpoint, options) {
    const { type } = endpoint;
    if (typeof type?.behaviors !== "object") {
      throw new import_general.ImplementationError('EndpointType "behaviors" must be an array of Behavior.Type instances');
    }
    this.#endpoint = endpoint;
    this.#supported = type.behaviors;
    this.#options = options;
    for (const id in this.#supported) {
      const type2 = this.#supported[id];
      if (!(type2.prototype instanceof import_Behavior.Behavior)) {
        throw new import_general.ImplementationError(`${endpoint}.${id}" is not a Behavior.Type`);
      }
      if (typeof type2.id !== "string") {
        throw new import_general.ImplementationError(`${endpoint}.${id} has no ID`);
      }
      this.#augmentEndpoint(type2);
    }
  }
  /**
   * Activate any behaviors designated for immediate activation.  Returns a promise iff any behaviors have ongoing
   * initialization.
   *
   * Throws an error if any behavior crashes, but we allow all behaviors to settle before throwing.  The goal is to
   * surface multiple configuration errors and prevent inconsistent state caused by partial initialization.
   */
  initialize() {
    if (!this.#endpoint.lifecycle.isInstalled) {
      throw new import_general.ImplementationError(`Cannot initialize behaviors because endpoint is not installed`);
    }
    const initializeBehaviors = (context) => {
      const agent = this.#endpoint.agentFor(context);
      for (const type of Object.values(this.supported)) {
        if (type.early) {
          this.activate(type, agent);
        }
      }
      let promise2 = import_general.Construction.all(
        {
          [Symbol.iterator]: () => {
            return Object.values(this.#backings)[Symbol.iterator]();
          }
        },
        (causes) => new import_errors.EndpointBehaviorsError(causes)
      );
      const endpointInitializer = this.#endpoint.env.get(import_EndpointInitializer.EndpointInitializer);
      if (promise2) {
        promise2 = promise2.then(() => endpointInitializer.behaviorsInitialized(agent));
      } else {
        promise2 = endpointInitializer.behaviorsInitialized(agent);
      }
      return promise2;
    };
    const activity = this.#endpoint.env.get(import_NodeActivity.NodeActivity);
    let promise = import_LocalActorContext.LocalActorContext.act(`initialize<${this.#endpoint}>`, initializeBehaviors, {
      activity,
      lifetime: this.#endpoint.construction
    });
    const onReady = () => {
      this.#endpoint.lifecycle.change(import_EndpointLifecycle.EndpointLifecycle.Change.Ready);
    };
    if (promise) {
      promise = promise.then(onReady);
    } else {
      onReady();
    }
    return promise;
  }
  has(type) {
    if (typeof type === "string") {
      return !!this.#supported[type];
    }
    const myType = this.#supported[type.id];
    return myType === type || myType?.supports(type);
  }
  /**
   * Add behavior support dynamically at runtime.  Typically called via {@link Agent.require}.
   */
  require(type, options) {
    if (this.#supported[type.id]) {
      if (!this.has(type)) {
        throw new import_general.ImplementationError(
          `Cannot require ${this.#endpoint}.${type.id} because incompatible implementation already exists`
        );
      }
      return;
    }
    this.inject(type, options);
  }
  /**
   * Create a behavior synchronously.  Fails if the behavior is not fully initialized.
   */
  createSync(type, agent) {
    const behavior = this.createMaybeAsync(type, agent);
    if (import_general.MaybePromise.is(behavior)) {
      throw new import_general.ImplementationError(
        `Synchronous access to ${this.#endpoint}.${type.id} is impossible because it is still initializing`
      );
    }
    return behavior;
  }
  /**
   * Create a behavior asynchronously.  Waits for the behavior to complete initialization.
   */
  async createAsync(type, agent) {
    return import_general.MaybePromise.then(
      () => this.createMaybeAsync(type, agent),
      void 0,
      (e) => {
        const backing = this.#backings[type.id];
        if (!backing) {
          throw e;
        }
        backing.construction.assert(backing.toString());
      }
    );
  }
  /**
   * Create a behavior, possibly asynchronously.
   *
   * This method returns a {@link Promise} only if await is necessary so the behavior can be used immediately if
   * possible.
   */
  createMaybeAsync(type, agent) {
    this.activate(type, agent);
    const backing = this.#backings[type.id];
    const getBehavior = () => {
      if (agent.isLoaded(type)) {
        return agent.get(type);
      }
      return backing.createBehavior(agent, type);
    };
    if (backing.construction.status === import_general.Lifecycle.Status.Initializing) {
      return backing.construction.then(() => getBehavior()).catch(() => {
        backing.construction.assert(backing.toString());
        return getBehavior();
      });
    }
    backing.construction.assert(backing.toString());
    return getBehavior();
  }
  /**
   * Activate a behavior.
   *
   * Semantically identical to createAsync() but does not return a {@link Promise} or throw an error.
   *
   * Behaviors that fail initialization will be marked with crashed {@link status}.
   */
  activate(type, agent) {
    if (!this.#backings[type.id]) {
      this.#createBacking(type, agent);
    }
  }
  /**
   * Determine if a specified behavior is supported and active.
   */
  isActive(type) {
    if (typeof type === "string") {
      return this.#backings[type] !== void 0;
    }
    const backing = this.#backings[type.id];
    return !!backing && backing.type.supports(type);
  }
  /**
   * Destroy all behaviors that are initialized (have backings present).  The object may be reused after close.
   */
  async close() {
    const dispose = async (context) => {
      const agent = this.#endpoint.agentFor(context);
      let destroyNow = new Set(Object.keys(this.#backings));
      while (destroyNow.size) {
        for (const key in this.#backings) {
          const dependencies = this.#backings[key].type.dependencies;
          if (!dependencies) {
            continue;
          }
          for (const type of dependencies) {
            destroyNow.delete(type.id);
          }
          if (!destroyNow.size) {
            throw new import_general.ImplementationError("Cannot destroy behaviors due to circular dependency");
          }
        }
        for (const id of destroyNow) {
          const backing = this.#backings[id];
          this.#protocol?.deleteCluster(backing);
          await this.#backings[id].close(agent);
          delete this.#backings[id];
        }
        destroyNow = new Set(Object.keys(this.#backings));
      }
      const transaction = agent.context.transaction;
      if (transaction.status === import_general.Transaction.Status.Exclusive) {
        await transaction.commit();
      }
    };
    await import_LocalActorContext.LocalActorContext.act(
      `close<${this.#endpoint}>`,
      dispose,
      {
        lifetime: this.#endpoint.construction
        // Note - do not close in an activity because this can cause deadlock
        // activity: this.#endpoint.env.get(NodeActivity),
      }
    );
  }
  /**
   * Add support for an additional behavior.
   *
   * This should generally only be used prior to initialization.  It may cause subtle errors if incompatible types are
   * injected once the endpoint is initialized.
   */
  inject(type, options, notify = true) {
    if (options) {
      this.#options[type.id] = options;
    }
    if (this.#supported === this.#endpoint.type.behaviors) {
      this.#supported = { ...this.#supported };
    }
    this.#supported[type.id] = type;
    this.#augmentEndpoint(type);
    if (notify) {
      this.#endpoint.lifecycle.change(import_EndpointLifecycle.EndpointLifecycle.Change.ServersChanged);
    }
    if (!this.#endpoint.lifecycle.isInstalled) {
      return;
    }
    const activeBacking = this.#backings[type.id];
    if (activeBacking) {
      activeBacking.type = type;
    } else if (type.early) {
      this.#activateLate(type);
    }
  }
  /**
   * Drop support for a behavior.
   *
   * This is intended for synchronization with peers and should not be used for servers as Matter does not allow an
   * endpoint to change its set of supported clusters.
   */
  drop(id) {
    const supported = this.#supported[id];
    if (!supported) {
      return;
    }
    const type = this.#supported[id];
    delete this.#supported[id];
    let promise;
    const backing = this.#backings[id];
    if (backing) {
      logger.info(`Removing ${backing} from active endpoint`);
      promise = backing.close();
      delete this.#backings[id];
    }
    this.#endpoint.lifecycle.change(import_EndpointLifecycle.EndpointLifecycle.Change.ServersChanged);
    const events = this.#events[id];
    if (events) {
      let detachedObservers;
      for (const key in events) {
        const observable = events[key];
        if (observable && "detachObservers" in observable) {
          const detached = observable.detachObservers();
          if (detached) {
            (detachedObservers ??= {})[key] = detached;
          }
        }
      }
      if (detachedObservers) {
        (this.#detachedObservers ??= {})[id] = detachedObservers;
      }
      delete this.#events[id];
    }
    delete this.#endpoint.state[id];
    if (type.schema.id !== void 0) {
      delete this.#endpoint.state[type.schema.id];
    }
    delete this.#endpoint.events[id];
    return promise;
  }
  /**
   * Ensure a set of behavior requirements are met.  Throws an error detailing missing requirements.
   */
  validateRequirements(requirements) {
    if (!requirements) {
      return;
    }
    const missing = Array();
    for (const requirement of Object.values(requirements)) {
      let name = (0, import_general.camelize)(requirement.name, true);
      if (this.#endpoint.behaviors.has(requirement)) {
        continue;
      }
      const cluster = clusterOf(requirement);
      if (cluster) {
        const other = this.#endpoint.behaviors.supported[requirement.id];
        if (clusterOf(other)?.id === cluster.id) {
          continue;
        }
        name = `${name} (${import_general.Diagnostic.hex(cluster.id)})`;
      }
      missing.push(name);
    }
    if (missing.length) {
      throw new import_general.ImplementationError(
        `${this.#endpoint} is missing required behaviors: ${(0, import_general.describeList)("and", ...missing)}`
      );
    }
  }
  /**
   * Obtain default values for a behavior.  This is state values as present when the behavior is first initialized for
   * a new endpoint.
   */
  defaultsFor(type) {
    let defaults;
    const options = this.#options[type.id];
    if (options) {
      for (const key in type.defaults) {
        if (key in options) {
          if (!defaults) {
            defaults = {};
          }
          defaults[key] = options[key];
        }
      }
    }
    const varService = this.#endpoint.env.get(import_EndpointVariableService.EndpointVariableService);
    const vars = varService.forBehaviorInstance(this.#endpoint, type);
    if (vars !== void 0) {
      defaults = { ...defaults, ...type.supervisor.cast(vars) };
    }
    return defaults;
  }
  /**
   * Retrieve the options for a behavior type provided to the endpoint.
   */
  optionsFor(type) {
    return this.#options[type.id];
  }
  /**
   * Access internal state for a {@link Behavior}.
   *
   * Internal state is not a stable API and not intended for consumption outside the behavior.  However, it is not
   * truly private and may be accessed by tightly coupled implementation.
   *
   * As this API is intended for use by "friendly" code, it does not perform the same initialization assertions as
   * does access to {@link Behavior.State} and {@link Behavior.Events}.
   */
  internalsOf(type) {
    const backing = this.#backingFor(type);
    return backing.getInternal();
  }
  versionOf(type) {
    if (typeof type === "string") {
      const backing2 = this.#backings[type];
      return backing2?.maybeDatasource?.version;
    }
    const backing = this.#backingFor(type);
    return backing.datasource.version;
  }
  /**
   * Access elements supported by a behavior.
   */
  elementsOf(type) {
    if (!this.has(type)) {
      throw new import_general.ImplementationError(`Endpoint ${this.#endpoint} does not support behavior ${type.id}`);
    }
    const elements = this.#backingFor(type).elements;
    if (elements === void 0) {
      throw new import_general.ImplementationError(
        `Endpoint ${this.#endpoint} behavior ${type.id} elements accessed before initialization`
      );
    }
    return elements;
  }
  /**
   * Access the state view of a behavior if loaded.
   */
  maybeStateOf(behaviorId) {
    const backing = this.#backings[behaviorId];
    return backing?.maybeDatasource?.view;
  }
  [Symbol.iterator]() {
    return Object.values(this.#supported)[Symbol.iterator]();
  }
  #activateLate(type) {
    const result = import_LocalActorContext.LocalActorContext.act(
      "behavior-late-activation",
      (context) => {
        this.activate(type, this.#endpoint.agentFor(context));
        const backing = this.#backingFor(type);
        return backing.construction.ready;
      },
      { lifetime: this.#endpoint.construction }
    );
    if (import_general.MaybePromise.is(result)) {
      result.then(void 0, (error) => {
        const backing = this.#backings[type.id];
        if (error instanceof import_errors.BehaviorInitializationError) {
          logger.error(error);
        } else if (backing) {
          logger.error(`Error initializing ${backing}:`, error);
        } else {
          logger.error(`Unexpected rejection initializing ${type.name}:`, error);
        }
      });
    }
  }
  /**
   * Create a read-only online view of a behavior.
   */
  createOnlineView(type) {
    return this.#backingFor(type).datasource;
  }
  /**
   * Get backing for a behavior.
   */
  #backingFor(type) {
    if (this.#endpoint.construction.status !== import_general.Lifecycle.Status.Initializing) {
      this.#endpoint.construction.assert(this.#endpoint.toString(), `behavior ${type.id}`);
    }
    let backing = this.#backings[type.id];
    if (!backing) {
      this.#activateLate(type);
      backing = this.#backings[type.id];
      if (backing === void 0) {
        throw new import_errors.BehaviorInitializationError(`${this.#endpoint}.${type.id}`, "initialization failed");
      }
    }
    return backing;
  }
  #createBacking(type, agent) {
    const myType = this.#getBehaviorType(type);
    if (!myType) {
      throw new import_errors.BehaviorInitializationError(`Initializing ${this.#endpoint}.${type.id}: Unsupported behavior`);
    }
    const backing = this.#endpoint.env.get(import_EndpointInitializer.EndpointInitializer).createBacking(this.#endpoint, myType);
    this.#backings[backing.type.id] = backing;
    if (backing.type !== myType) {
      this.#supported[backing.type.id] = backing.type;
    }
    if (!this.#protocol) {
      this.#protocol = this.#endpoint.env.get(import_ProtocolService.ProtocolService);
    }
    backing.initializeDataSource();
    backing.construction.start(agent);
    return backing;
  }
  #getBehaviorType(type) {
    const myType = this.#supported[type.id];
    if (myType === void 0) {
      return myType;
    }
    if (typeof myType !== "function" || !(myType.prototype instanceof import_Behavior.Behavior)) {
      throw new import_general.ImplementationError(`Endpoint behavior "${type.id}" implementation is not a Behavior`);
    }
    return myType;
  }
  /**
   * Updates endpoint "state" and "events" properties to include properties for a supported behavior.
   */
  #augmentEndpoint(type) {
    const { id, Events: Events2 } = type;
    const get = () => this.#backingFor(type).stateView;
    Object.defineProperty(this.#endpoint.state, id, { get, enumerable: true, configurable: true });
    if (type.schema.id !== void 0) {
      Object.defineProperty(this.#endpoint.state, type.schema.id, { get, configurable: true });
    }
    const detachedObservers = this.#detachedObservers?.[type.id];
    if (detachedObservers) {
      delete this.#detachedObservers[type.id];
      const newEvents = new Events2();
      for (const key in detachedObservers) {
        const newEvent = newEvents[key];
        if (newEvent && "attachObservers" in newEvent) {
          newEvent.attachObservers(detachedObservers);
        }
      }
      this.#events[id] = newEvents;
    }
    Object.defineProperty(this.#endpoint.events, id, {
      get: () => {
        let events = this.#events[id];
        if (!events) {
          events = this.#events[id] = new Events2();
          if (typeof events.setContext === "function") {
            events.setContext(this.#endpoint, type);
          }
        }
        return events;
      },
      enumerable: true,
      configurable: true
    });
  }
}
function clusterOf(behavior) {
  return behavior?.cluster;
}
//# sourceMappingURL=Behaviors.js.map
