/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { INSTALL_BEHAVIOR } from "#endpoint/Agent.js";
import {
  EventEmitter,
  GeneratedClass,
  ImplementationError,
  NotImplementedError,
  Observable
} from "#general";
import { assertSecureSession } from "@matter.js/protocol";
import { DerivedState, EmptyState } from "./state/StateType.js";
import { Resource } from "./state/transaction/Resource.js";
import { BehaviorSupervisor } from "./supervision/BehaviorSupervisor.js";
const BACKING = Symbol("endpoint-owner");
const STATE = Symbol("state");
const INTERNAL = Symbol("internal");
const EVENTS = Symbol("events");
const SUPERVISOR = Symbol("supervisor");
class Behavior {
  #agent;
  /**
   * Each behavior implementation has an ID that uniquely identifies the type of behavior.  An Endpoint may only have
   * one behavior with the specified ID.
   *
   * Endpoint instances store each behavior in a property with the same name as the behavior's ID.
   *
   * EndpointBuilder also uses the ID when replacing behaviors using the with() builder method.
   */
  static id;
  /**
   * A behavior's schema controls access to data, commands and events.
   *
   * Schema is inferred from the methods and properties of the behavior but you can specify explicitly for additional
   * control.
   */
  static schema;
  /**
   * By default behaviors load lazily as they are accessed.  You can set this flag to true to force behaviors to load
   * immediately when the endpoint initializes.
   */
  static early = false;
  /**
   * The agent that owns the behavior.
   */
  get agent() {
    return this.#agent;
  }
  /**
   * The endpoint that owns behavior's agent.
   */
  get endpoint() {
    return this.#agent.endpoint;
  }
  /**
   * The context in which the behavior operates.
   */
  get context() {
    return this.#agent.context;
  }
  /**
   * The endpoint's environment.
   */
  get env() {
    return this.endpoint.env;
  }
  /**
   * The session in which the behavior has been invoked.
   */
  get session() {
    const session = this.#agent.context.session;
    if (session === void 0) {
      throw new ImplementationError(`Illegal operation outside session context`);
    }
    assertSecureSession(session);
    return session;
  }
  /**
   * Execute logic with elevated privileges.
   *
   * The provided function executes with privileges escalated to offline mode.  This is not commonly necessary.
   *
   * Elevated logic effectively ignores ACLs so should be used with care.
   *
   * Note that interactions with the behavior will remain elevated until the synchronous completion of this call.
   * You should only elevate privileges for synchronous logic.
   *
   * @param fn the elevated logic
   */
  asAdmin(fn) {
    const context = this.context;
    const offline = context.offline;
    try {
      context.offline = true;
      fn();
    } finally {
      context.offline = offline;
    }
  }
  constructor(agent, backing) {
    this.#agent = agent;
    this[BACKING] = backing;
    if (agent !== void 0) {
      agent[INSTALL_BEHAVIOR](this);
    }
  }
  /**
   * The data supervisor for the behavior.  The supervisor controls validation and access to behavior data.
   */
  static get supervisor() {
    if (Object.hasOwn(this, SUPERVISOR)) {
      return this[SUPERVISOR];
    }
    return this[SUPERVISOR] = BehaviorSupervisor(this);
  }
  /**
   * Implementation of endpoint-scoped state.  Subclasses may override to extend.
   */
  static State = EmptyState;
  /**
   * Implementation of internal state.  Subclasses may override to extend.
   */
  static Internal = EmptyState;
  /**
   * Implementation of the events property.  Subclasses may override to extend.
   */
  static Events = EventEmitter;
  /**
   * Behaviors are ephemeral and should not perform initialization in their constructor.  They can override this
   * method instead.
   *
   * This method may be synchronous or asyncronous.  If asynchronous, the behavior will not be available for external
   * use until initialization completes.
   */
  initialize(_options) {
  }
  /**
   * Release resources.  This is the public API for releasing application resources held by behaviors in internal
   * state.
   */
  [Symbol.asyncDispose]() {
  }
  /**
   * Description used in diagnostic messages.
   */
  toString() {
    return `${this.endpoint}.${this.constructor.id}`;
  }
  /**
   * Install a {@link Reactor}.
   *
   * Important: The reactor MUST be a real JS function - arrow functions will not work!
   */
  reactTo(observable, reactor, options) {
    this[BACKING].reactTo(observable, reactor, options);
  }
  /**
   * Create a generic callback function that has the same properties as a {@link Reactor}.
   *
   * Like a reactor, the callback's "this" will be bound to an active Behavior instance.
   * Because of this: The reactor MUST be a real JS function - arrow functions will not work!
   */
  callback(reactor, options) {
    const observable = Observable();
    this.reactTo(observable, reactor, options);
    return (...args) => observable.emit(...args);
  }
  /**
   * Does this behavior support functionality of a specific implementation?
   */
  static supports(other) {
    return this === other || this.prototype instanceof other;
  }
  /**
   * Default state values.
   */
  static get defaults() {
    return new this.State();
  }
  /**
   * Create a new behavior with different default state values.
   */
  static set(defaults) {
    return GeneratedClass({
      name: this.name,
      base: this,
      staticProperties: {
        State: DerivedState({
          name: `${this.name}$State`,
          base: this.State,
          values: defaults
        })
      }
    });
  }
  /**
   * Explicitly reference other {@link Behavior}s as dependencies, ensuring this behavior is destroyed first.
   *
   * This probably won't be commonly necessary.  If it is we can instrument Agent to collect dependencies
   * automatically.
   */
  static dependencies;
  get [Resource.reference]() {
    return this[BACKING].datasource;
  }
}
Object.defineProperties(Behavior.prototype, {
  state: {
    get() {
      if (!this[STATE]) {
        this[STATE] = this[BACKING].datasource.reference(this.context);
      }
      return this[STATE];
    },
    enumerable: true
  },
  internal: {
    get() {
      if (!this[INTERNAL]) {
        this[INTERNAL] = this[BACKING].getInternal();
      }
      return this[INTERNAL];
    },
    enumerable: false
  },
  events: {
    get() {
      if (!this[EVENTS]) {
        this[EVENTS] = this[BACKING].events;
      }
      return this[EVENTS];
    },
    enumerable: true
  }
});
((Behavior2) => {
  function unimplemented(..._args) {
    throw new NotImplementedError();
  }
  Behavior2.unimplemented = unimplemented;
})(Behavior || (Behavior = {}));
export {
  Behavior
};
//# sourceMappingURL=Behavior.js.map
