/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { ChangeNotificationService } from "#node/integration/ChangeNotificationService.js";
import {
  BasicObservable,
  EventEmitter,
  ImplementationError,
  InternalError,
  Logger,
  MaybePromise,
  Observable,
  QuietObservable,
  Time
} from "@matter/general";
import { ElementTag, EventElement } from "@matter/model";
import { OccurrenceManager } from "@matter/protocol";
import { ClusterId, EventId } from "@matter/types";
const logger = Logger.get("Events");
class Events extends EventEmitter {
  #endpoint;
  #behavior;
  #changes;
  setContext(endpoint, behavior) {
    this.#endpoint = endpoint;
    this.#behavior = behavior;
  }
  static {
    for (const name of ["interactionBegin", "interactionEnd", "stateChanged"]) {
      Object.defineProperty(this.prototype, name, {
        get() {
          if (this.hasEvent(name, true)) {
            return this.getEvent(name);
          }
          const event = Observable();
          this.addEvent(name, event);
          return event;
        },
        enumerable: true
      });
    }
  }
  get endpoint() {
    return this.#endpoint;
  }
  get behavior() {
    return this.#behavior;
  }
  get changes() {
    if (this.#changes === void 0 && this.#endpoint !== void 0 && this.#endpoint.env.has(ChangeNotificationService)) {
      this.#changes = this.#endpoint.env.get(ChangeNotificationService);
    }
    return this.#changes;
  }
  toString() {
    return `${this.#endpoint ?? "?"}.${this.#behavior?.id ?? "?"}.events`;
  }
}
class ElementEvent extends BasicObservable {
  #schema;
  #owner;
  constructor(schema, owner) {
    super();
    this.#schema = schema;
    this.#owner = owner;
  }
  /**
   * The element that triggers this event.
   */
  get schema() {
    return this.#schema;
  }
  get owner() {
    return this.#owner;
  }
}
class OfflineEvent extends ElementEvent {
  constructor(schema, owner) {
    super(schema, owner);
  }
}
class OnlineEvent extends ElementEvent {
  isQuieter = false;
  #baseOccurrence;
  #occurrenceTrigger;
  handleError(error, observer) {
    logger.warn(`Error in ${descriptionOf(this, observer)}`, error);
  }
  constructor(schema, owner) {
    super(schema, owner);
    const eventSchema = this.schema;
    if (this.schema.tag === ElementTag.Event && this.schema.id !== void 0 && this.owner.endpoint !== void 0 && this.owner.behavior !== void 0 && eventSchema.priority !== void 0) {
      this.#baseOccurrence = {
        eventId: EventId(this.schema.id),
        clusterId: ClusterId(this.owner.behavior.schema.id),
        endpointId: this.owner.endpoint.number,
        priority: EventElement.PriorityId[eventSchema.priority]
      };
      this.#connectWithOccurenceManager();
    }
  }
  #connectWithOccurenceManager() {
    if (this.owner.endpoint === void 0) {
      throw new InternalError("Events initialized with no assigned endpoint");
    }
    const occurrenceManager = this.owner.endpoint.env.maybeGet(OccurrenceManager);
    if (occurrenceManager === void 0) {
      return;
    }
    const trigger = (payload) => {
      const occurrence = occurrenceManager.add({
        ...this.#baseOccurrence,
        epochTimestamp: Time.nowMs,
        payload
      });
      if (MaybePromise.is(occurrence)) {
        this.owner.endpoint.env.runtime.add(occurrence.then(this.#broadcast.bind(this)));
      } else {
        this.#broadcast(occurrence);
      }
    };
    this.online.on(trigger);
    this.#occurrenceTrigger = trigger;
  }
  #broadcast({ number, epochTimestamp: timestamp, priority, payload }) {
    this.owner.changes?.broadcastEvent(this.owner.endpoint, this.owner.behavior, this.schema, {
      number,
      timestamp,
      priority,
      payload
    });
  }
  /**
   * An {@link Observable} that emits only those events conveyed between nodes.
   *
   * Normally this is the {@link OnlineEvent}, but in the case of server-side elements that are
   * {@link Quality.quieter} this is {@link quiet}.
   */
  get online() {
    return this;
  }
  /**
   * A quieter version of the {@link OnlineEvent}.
   *
   * Throws if the node is not a server or the associated element is not {@link Quality.quieter}.
   *
   * By default emits latest changes once per second but you can reconfigure via {@link QuietObservable} properties
   * and/or trigger emits using {@link QuietObservable.emitNow} and {@link QuietObservable.emitSoon}.
   */
  get quiet() {
    throw new ImplementationError(`Matter does not define ${this} with "quieter" (Q) quality`);
  }
  toString() {
    const base = `${this.owner.toString()}.${this.schema.propertyName}`;
    if (this.schema.tag === ElementTag.Attribute || this.schema.tag === ElementTag.Field) {
      return `${base}$Changed`;
    }
    return base;
  }
  [Symbol.dispose]() {
    if (this.#occurrenceTrigger) {
      this.online.off(this.#occurrenceTrigger);
      this.#occurrenceTrigger = void 0;
    }
  }
}
class QuietEvent extends OnlineEvent {
  isQuieter = true;
  #quiet;
  constructor(schema, owner, config) {
    super(schema, owner);
    this.#quiet = new QuietObservable({
      shouldEmit(...args) {
        const [newValue, oldValue] = args;
        return oldValue === null || newValue === null && oldValue !== newValue ? "now" : true;
      },
      ...config,
      source: this
    });
  }
  get online() {
    return this.#quiet;
  }
  get quiet() {
    return this.#quiet;
  }
  [Symbol.dispose]() {
    this.#quiet[Symbol.dispose]();
    super[Symbol.dispose]();
  }
}
function descriptionOf(observable, observer) {
  let desc = `${observable} observer`;
  if (observer.name) {
    desc = `${desc} ${observer.name}`;
  }
  return desc;
}
export {
  ElementEvent,
  Events,
  OfflineEvent,
  OnlineEvent,
  QuietEvent
};
//# sourceMappingURL=Events.js.map
