"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 BehaviorBacking_exports = {};
__export(BehaviorBacking_exports, {
  BehaviorBacking: () => BehaviorBacking
});
module.exports = __toCommonJS(BehaviorBacking_exports);
var import_cluster_behavior_utils = require("#behavior/cluster/cluster-behavior-utils.js");
var import_Migration = require("#behavior/state/migrations/Migration.js");
var import_errors = require("#endpoint/errors.js");
var import_general = require("#general");
var import_ChangeNotificationService = require("#node/integration/ChangeNotificationService.js");
var import_ProtocolService = require("#node/integration/ProtocolService.js");
var import_Datasource = require("../state/managed/Datasource.js");
var import_BackingEvents = require("./BackingEvents.js");
var import_Reactors = require("./Reactors.js");
/**
 * @license
 * Copyright 2022-2025 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("BehaviorBacking");
class BehaviorBacking {
  #endpoint;
  #type;
  #protocol;
  #changeTracking;
  #internal;
  #events;
  #options;
  #datasource;
  #reactors;
  #construction;
  #suppressedChanges;
  #quietObservers;
  get construction() {
    return this.#construction;
  }
  constructor(endpoint, type, store, options) {
    this.#endpoint = endpoint;
    this.#type = type;
    this.#protocol = endpoint.env.get(import_ProtocolService.ProtocolService);
    this.#changeTracking = endpoint.env.get(import_ChangeNotificationService.ChangeNotificationService);
    this.store = store;
    this.#options = options;
    this.#configureEventSuppression();
    this.#construction = (0, import_general.Construction)(this);
    this.#construction.onError((error) => {
      if (endpoint.lifecycle.isReady) {
        if (error instanceof import_errors.BehaviorInitializationError) {
          logger.error(error);
        } else {
          logger.error(`Error initializing ${this}:`, error);
        }
      }
    });
  }
  toString() {
    return this.path.toString();
  }
  get path() {
    return this.#endpoint.path.at(this.type.id);
  }
  /**
   * Initialize state by applying values from options and invoking the behavior's initialize() function.
   *
   * Initiated via {@link Construction#start} by Behaviors class once the backing is installed.
   */
  [import_general.Construction.construct](agent) {
    let crashError;
    const crash = (cause) => {
      if (!crashError) {
        crashError = new import_errors.BehaviorInitializationError(`Error initializing ${this}`, cause);
      }
      throw crashError;
    };
    try {
      const behavior = this.#lifecycleInstance(agent);
      const promise = this.invokeInitializer(behavior, this.#options);
      return import_general.MaybePromise.then(promise, () => this.#protocol.addCluster(this), crash);
    } catch (e) {
      crash(e);
    }
  }
  get [import_general.Lifetime.owner]() {
    return this.#endpoint.construction;
  }
  initializeDataSource() {
    if (this.#datasource) {
      return;
    }
    if (this.store.initialValues !== void 0) {
      import_Migration.Migration.migrate(this.type, this.store.initialValues);
    }
    this.#datasource = (0, import_Datasource.Datasource)(this.datasourceOptions);
  }
  /**
   * Destroy the backing.
   */
  close(agent) {
    const initialized = this.construction.status === import_general.Lifecycle.Status.Active;
    if (!initialized) {
      agent = void 0;
    }
    return this.construction.close(() => {
      this.#quietObservers?.close();
      let result = import_general.MaybePromise.then(
        () => this.#reactors?.close(),
        () => {
          this.#reactors = void 0;
        }
      );
      if (agent) {
        result = import_general.MaybePromise.then(result, () => this.#invokeClose(agent));
      }
      return result;
    });
  }
  /**
   * Set state from options and invoke {@link Behavior.invokeInitializer}.
   *
   * This is an optional extension point for derivatives.  Errors thrown here are recorded and place the behavior into
   * crashed state.
   */
  invokeInitializer(behavior, options) {
    return behavior.initialize(options);
  }
  /**
   * The {@link Endpoint} that owns the behavior.
   */
  get endpoint() {
    return this.#endpoint;
  }
  /**
   * The {@link Behavior.Type} backed.
   */
  get type() {
    return this.#type;
  }
  set type(type) {
    if (!type.supports(this.#type)) {
      logger.warn(
        `The cluster for active behavior ${this} may no longer be strictly compatible with local implementation`
      );
    }
    this.#type = type;
  }
  /**
   * Create an instance of the backed {@link Behavior}.
   *
   * Derivatives may override to perform additional setup beyond simple instantiation.
   */
  createBehavior(agent, type) {
    const behavior = new this.#type(agent, this);
    if (behavior instanceof type || (0, import_cluster_behavior_utils.isClientBehavior)(type)) {
      return behavior;
    }
    throw new import_general.ImplementationError(
      `Cannot create ${this.#endpoint}.${type.id} because installed implementation is incompatible`
    );
  }
  /**
   * The source of raw data that backs managed state instances.
   */
  get datasource() {
    if (!this.#datasource) {
      throw new import_general.InternalError("Datasource not yet initialized");
    }
    return this.#datasource;
  }
  /**
   * Access the datasource if present.
   */
  get maybeDatasource() {
    return this.#datasource;
  }
  get datasourceOptions() {
    return {
      entropy: this.#endpoint.env.get(import_general.Entropy),
      location: {
        path: this.#endpoint.path.at(this.#type.id).at("state"),
        endpoint: this.#endpoint.number,
        cluster: this.type.schema.tag === "cluster" ? this.type.schema.id : void 0
      },
      supervisor: this.type.supervisor,
      type: this.type.State,
      events: this.events,
      defaults: this.#endpoint.behaviors.defaultsFor(this.type),
      store: this.store,
      owner: this.#endpoint,
      onChange: this.#onChange.bind(this)
    };
  }
  /**
   * The data provider for {@link datasource}.
   */
  store;
  /**
   * Obtain internal state for a behavior instance.
   */
  getInternal() {
    if (!this.#internal) {
      this.#internal = new this.#type.Internal();
    }
    return this.#internal;
  }
  /**
   * Access the event object.  Unlike state, the events object does not vary by instance.
   */
  get events() {
    if (!this.#events) {
      this.#events = (0, import_BackingEvents.BackingEvents)(this);
    }
    return this.#events;
  }
  /**
   * The status of the behavior.
   */
  get status() {
    return this.construction.status;
  }
  /**
   * A read-only offline view of behavior state.
   */
  get stateView() {
    return this.datasource.view ?? {};
  }
  /**
   * Install a reactor.
   */
  reactTo(observable, reactor, options) {
    if (!this.#reactors) {
      this.#reactors = new import_Reactors.Reactors(this);
    }
    this.#reactors.add(observable, reactor, options);
  }
  /**
   * Terminate reactions.
   */
  async stopReacting(selector) {
    if (this.#reactors === void 0) {
      return;
    }
    if (selector?.observable === void 0 && selector?.reactor === void 0) {
      await this.#reactors.close();
      this.#reactors = void 0;
      return;
    }
    await this.#reactors.remove(selector);
  }
  /**
   * We handle events in bulk via {@link Datasource.Options.onChange}, but "quieter" and "changesOmitted" events
   * require special handling.  Those we ignore in the change handler and instead report only when emitted by the
   * corresponding {@link OnlineEvent}.
   */
  #configureEventSuppression() {
    const { schema } = this.type;
    if (!schema) {
      return;
    }
    for (const property of schema.conformant.properties) {
      const { changesOmitted, quieter } = property.effectiveQuality;
      if (!changesOmitted && !quieter) {
        continue;
      }
      const name = (0, import_general.camelize)(property.name);
      if (!this.#suppressedChanges) {
        this.#suppressedChanges = /* @__PURE__ */ new Set();
      }
      this.#suppressedChanges.add(name);
      if (!quieter) {
        continue;
      }
      const event = this.events[`${name}$Changed`];
      if (event === void 0) {
        continue;
      }
      if (event.isQuieter) {
        if (!this.#quietObservers) {
          this.#quietObservers = new import_general.ObserverGroup();
        }
        this.#quietObservers.on(event.quiet, () => this.#broadcastChanges([name]));
      }
    }
  }
  /**
   * Invoke {@link Behavior.destroy} to clean up application logic.
   */
  #invokeClose(agent) {
    const behavior = this.#lifecycleInstance(agent);
    return import_general.MaybePromise.finally(
      () => import_general.MaybePromise.then(
        () => behavior?.[Symbol.asyncDispose](),
        void 0,
        (e) => logger.error(`Destroying ${this}:`, e)
      ),
      () => this.#events?.[Symbol.dispose]()
    );
  }
  /**
   * Obtain a behavior instance for lifecycle methods (initialize and close).
   *
   * Under these circumstances we can't use {@link Agent.get} because it will throw if the endpoint is initializing
   * or closing.
   *
   * Instead we use a "friend" method of agent to retrieve any existing behavior or create a new one.
   */
  #lifecycleInstance(agent) {
    if (agent.isLoaded(this.#type)) {
      return agent.get(this.#type);
    }
    const behavior = this.createBehavior(agent, this.#type);
    return behavior;
  }
  #onChange(props) {
    if (this.#suppressedChanges) {
      props = props.filter((name) => !this.#suppressedChanges.has(name));
    }
    this.#broadcastChanges(props);
  }
  /**
   * We provide two forms of optimized change tracking.
   *
   * {@link ProtocolService} offers a low-level service that supports the Matter protocol.
   *
   * {@link ChangeNotificationService} is a higher-level service that supports state synchronization use cases.
   *
   * This method informs these services of changes.
   */
  #broadcastChanges(props) {
    this.#protocol.handleChange(this, props);
    this.#changeTracking.broadcastUpdate(this, props);
  }
}
//# sourceMappingURL=BehaviorBacking.js.map
