"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 Observable_exports = {};
__export(Observable_exports, {
  AsyncObservable: () => AsyncObservable,
  AsyncObservableValue: () => AsyncObservableValue,
  BasicObservable: () => BasicObservable,
  BasicObservableValue: () => BasicObservableValue,
  EventEmitter: () => EventEmitter,
  Observable: () => Observable,
  ObservableProxy: () => ObservableProxy,
  ObservableValue: () => ObservableValue,
  ObserverGroup: () => ObserverGroup,
  QuietObservable: () => QuietObservable,
  observant: () => observant
});
module.exports = __toCommonJS(Observable_exports);
var import_Diagnostic = require("#log/Diagnostic.js");
var import_Time = require("#time/Time.js");
var import_TimeUnit = require("#time/TimeUnit.js");
var import_MatterError = require("../MatterError.js");
var import_Logger = require("../log/Logger.js");
var import_disposable = require("../polyfills/disposable.js");
var import_Error = require("./Error.js");
var import_Promises = require("./Promises.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_Logger.Logger.get("Observable");
const observant = /* @__PURE__ */ Symbol("consider-observed");
function defaultErrorHandler(error) {
  throw error;
}
class BasicObservable {
  #handleError;
  #isAsync;
  #handlePromise;
  #observers;
  #once;
  #instrumentAs;
  #joinIteration;
  #removeIterator;
  #stopIteration;
  constructor(handleError, asyncConfig) {
    this.handleError = handleError ?? defaultErrorHandler;
    if (typeof asyncConfig === "function") {
      this.handlePromise = asyncConfig;
    } else {
      this.isAsync = asyncConfig ?? false;
    }
  }
  set traceAs(name) {
    this.#instrumentAs = name;
  }
  [Symbol.dispose]() {
    this.#observers = this.#once = void 0;
    this.#stopIteration?.();
  }
  set handleError(handleError) {
    this.#handleError = handleError;
  }
  get handleError() {
    return this.#handleError;
  }
  set isAsync(isAsync) {
    this.#isAsync = isAsync;
    if (isAsync) {
      this.#handlePromise = (promise) => promise;
    } else {
      this.#handlePromise = (promise, observer) => {
        promise.catch((error) => {
          let identity;
          if (observer.name) {
            identity = ` "${observer.name}"`;
          } else {
            identity = "";
          }
          if (this.#handleError === defaultErrorHandler) {
            logger.error(`Unhandled error in async observer${identity}:`, error);
          } else {
            this.#handleError(error, observer);
          }
        });
      };
    }
  }
  get isAsync() {
    return this.#isAsync;
  }
  set handlePromise(handlePromise) {
    this.isAsync = true;
    this.#handlePromise = handlePromise;
  }
  get handlePromise() {
    return this.#handlePromise;
  }
  get isObserved() {
    if (this.#observers) {
      for (const observer of this.#observers) {
        if (observer[observant] !== false) {
          return true;
        }
      }
    }
    if (this.#once) {
      for (const observer of this.#once) {
        if (observer[observant] !== false) {
          return true;
        }
      }
    }
    return false;
  }
  isObservedBy(observer) {
    return !!this.#observers?.has(observer);
  }
  emit(...payload) {
    if (!this.#observers) {
      return;
    }
    const observers = [...this.#observers];
    let nextObserver = 0;
    const next = () => {
      const observer = observers[nextObserver];
      if (this.#once?.has(observer)) {
        this.#once.delete(observer);
        this.#observers?.delete(observer);
      }
      if (this.#instrumentAs) {
        logger.debug(
          import_Diagnostic.Diagnostic.strong(this.#instrumentAs),
          `invoking #${nextObserver++}`,
          import_Diagnostic.Diagnostic.strong(observer.name || "(anon)")
        );
      }
      return observer;
    };
    let log, done;
    if (this.#instrumentAs) {
      logger.debug(import_Diagnostic.Diagnostic.strong(this.#instrumentAs), "emitting");
      log = (observer) => {
        logger.debug(
          import_Diagnostic.Diagnostic.strong(this.#instrumentAs),
          `invoking #${nextObserver++}`,
          import_Diagnostic.Diagnostic.strong(observer.name || "(anon)")
        );
      };
      done = () => {
        logger.debug(import_Diagnostic.Diagnostic.strong(this.#instrumentAs), "emission complete");
      };
    }
    for (; nextObserver < observers.length; nextObserver++) {
      let result;
      let observer = next();
      try {
        log?.(observer);
        result = observer(...payload);
      } catch (e) {
        this.#handleError((0, import_Error.asError)(e), observer);
      }
      if (result === void 0) {
        continue;
      }
      if (import_Promises.MaybePromise.is(result)) {
        const emitAsync = async () => {
          while (true) {
            if (import_Promises.MaybePromise.is(result)) {
              try {
                result = await result;
              } catch (e) {
                this.#handleError((0, import_Error.asError)(e), observer);
              }
            }
            if (result !== void 0) {
              return result;
            }
            nextObserver++;
            if (nextObserver >= observers.length) {
              break;
            }
            observer = next();
            try {
              result = observer(...payload);
            } catch (e) {
              this.#handleError((0, import_Error.asError)(e), observer);
            }
          }
          done?.();
        };
        return emitAsync();
      }
      done?.();
      return result;
    }
  }
  on(observer) {
    if (!this.#observers) {
      this.#observers = /* @__PURE__ */ new Set();
    }
    this.#observers.add(observer);
  }
  use(observer) {
    this.on(observer);
    return {
      [Symbol.dispose]: () => {
        this.off(observer);
      }
    };
  }
  useOnce(observer) {
    this.once(observer);
    return {
      [Symbol.dispose]: () => {
        this.off(observer);
      }
    };
  }
  off(observer) {
    this.#observers?.delete(observer);
    this.#once?.delete(observer);
  }
  once(observer) {
    this.on(observer);
    if (!this.#once) {
      this.#once = /* @__PURE__ */ new Set();
    }
    this.#once.add(observer);
  }
  then(onfulfilled, onrejected) {
    return new Promise((resolve) => {
      this.once((...payload) => {
        resolve(payload[0]);
      });
    }).then(onfulfilled, onrejected);
  }
  async *[Symbol.asyncIterator]() {
    let promise = this.#addIterator();
    try {
      while (promise) {
        const next = await promise;
        if (next) {
          promise = next.promise;
          yield next.value[0];
        }
      }
    } finally {
      this.#removeIterator?.();
    }
  }
  detachObservers() {
    if (!this.#observers) {
      return;
    }
    return {
      observers: this.#observers,
      once: this.#once
    };
  }
  attachObservers(detached) {
    if (!detached.observers) {
      return;
    }
    for (const observer of detached.observers) {
      if (this.#once?.has(observer)) {
        this.once(observer);
      } else {
        this.on(observer);
      }
    }
  }
  #addIterator() {
    if (this.#joinIteration) {
      return this.#joinIteration();
    }
    let resolve;
    let iteratorCount = 1;
    function newPromise() {
      return new Promise((r) => resolve = r);
    }
    let promise = newPromise();
    function observer(...args) {
      const oldResolve = resolve;
      promise = newPromise();
      oldResolve({ value: args[0], promise });
    }
    this.on(observer);
    this.#joinIteration = () => {
      iteratorCount++;
      return promise;
    };
    this.#removeIterator = () => {
      if (!iteratorCount--) {
        this.#stopIteration?.();
      }
    };
    this.#stopIteration = () => {
      this.off(observer);
      resolve(void 0);
      this.#stopIteration = void 0;
      this.#removeIterator = void 0;
    };
  }
}
const Observable = constructObservable;
function constructObservable(handleError) {
  return new BasicObservable(handleError);
}
const AsyncObservable = constructAsyncObservable;
function constructAsyncObservable(handleError) {
  return new BasicObservable(handleError, true);
}
function event(emitter, name) {
  const observer = emitter[name];
  if (typeof !observer?.on !== "function") {
    throw new import_MatterError.ImplementationError(`Invalid event name ${name}`);
  }
  return observer;
}
class BasicObservableValue extends BasicObservable {
  #value;
  #error;
  #awaiters;
  constructor(value, handleError, asyncConfig) {
    super(handleError, asyncConfig);
    this.#value = value;
    const maybeResolve = this.#maybeResolve.bind(this);
    Object.defineProperty(maybeResolve, observant, { value: false });
    this.on(maybeResolve);
  }
  get value() {
    return this.#value;
  }
  set value(value) {
    this.#maybeResolve([value]);
  }
  get error() {
    return this.#error;
  }
  reject(cause) {
    cause = (0, import_Error.asError)(cause);
    this.#value = void 0;
    this.#error = cause;
    const awaiters = this.#awaiters;
    if (awaiters) {
      this.#awaiters = void 0;
      for (const awaiter of awaiters) {
        awaiter.reject?.(cause);
      }
    }
  }
  #maybeResolve(value) {
    this.#value = value;
    if (!this.#value) {
      return;
    }
    const awaiters = this.#awaiters;
    if (awaiters) {
      this.#awaiters = void 0;
      for (const awaiter of awaiters) {
        awaiter.resolve?.(this.#value);
      }
    }
  }
  then(onfulfilled, onrejected) {
    if (this.#error) {
      return Promise.reject(this.#error).then(onfulfilled, onrejected);
    }
    if (this.#value) {
      return Promise.resolve(this.#value).then(onfulfilled, onrejected);
    }
    return new Promise((resolve, reject) => {
      if (!this.#awaiters) {
        this.#awaiters = [];
      }
      this.#awaiters.push({ resolve, reject });
    }).then(onfulfilled, onrejected);
  }
  catch(onrejected) {
    return this.then(void 0, onrejected);
  }
  onError(handler) {
    if (!this.#awaiters) {
      this.#awaiters = [];
    }
    this.#awaiters?.push({ resolve: void 0, reject: handler });
  }
  offError(handler) {
    this.#awaiters = this.#awaiters?.filter((awaiter) => awaiter.resolve === void 0 && awaiter.reject === handler);
  }
  useError(handler) {
    this.onError(handler);
    return {
      [Symbol.dispose]: () => {
        this.offError(handler);
      }
    };
  }
  finally(onfinally) {
    return Promise.resolve(this).finally(onfinally);
  }
  [Symbol.toStringTag] = "Promise";
}
const ObservableValue = constructObservableValue;
function constructObservableValue(value, handleError) {
  return new BasicObservableValue(value, handleError);
}
const AsyncObservableValue = constructAsyncObservableValue;
function constructAsyncObservableValue(value, handleError) {
  return new BasicObservableValue(value, handleError, true);
}
class EventEmitter {
  // True private screws up TS types
  events;
  emit(name, ...payload) {
    event(this, name).emit(...payload);
  }
  addListener(name, handler) {
    event(this, name).on(handler);
  }
  removeListener(name, handler) {
    event(this, name).off(handler);
  }
  addEvent(name, event2) {
    if (!this.events) {
      this.events = {};
    }
    this.events[name] = event2;
  }
  getEvent(name) {
    if (!this.events || !(name in this.events)) {
      throw new import_MatterError.ImplementationError(`No such event ${name}`);
    }
    return this.events[name] ?? (this.events[name] = Observable());
  }
  hasEvent(name, onlyIfInitialized = false) {
    return this.events && (onlyIfInitialized ? this.events[name] : name in this.events);
  }
  get eventNames() {
    return this.events ? Object.keys(this.events) : [];
  }
  [Symbol.dispose]() {
    if (!this.events) {
      return;
    }
    for (const event2 of Object.values(this.events)) {
      event2?.[Symbol.dispose]?.();
    }
    this.events = void 0;
  }
}
class ObservableProxy extends BasicObservable {
  #target;
  #emitter = super.emit.bind(this);
  constructor(target) {
    super();
    Object.defineProperty(this.#emitter, observant, {
      get() {
        return super.isObserved;
      }
    });
    this.#target = target;
    this.#target.on(this.#emitter);
    this.emit = this.#target.emit.bind(this.#target);
  }
  [Symbol.dispose]() {
    this.#target.off(this.#emitter);
    super[Symbol.dispose]();
  }
  get isObserved() {
    return this.#target.isObserved;
  }
  emit;
  get target() {
    return this.#target;
  }
}
class ObserverGroup {
  #defaultTarget;
  #observers = /* @__PURE__ */ new Map();
  #boundObservers = /* @__PURE__ */ new Map();
  constructor(target) {
    this.#defaultTarget = target;
  }
  /**
   * Add an observer.
   *
   * @param observable the observable to observe
   * @param observer the observer function
   * @param target optional "this" to bind the observer
   */
  on(observable, observer, target = this.#defaultTarget) {
    if (target !== void 0) {
      observer = observer.bind(target);
    }
    observable.on(observer);
    const observers = this.#observers.get(observable);
    if (observers === void 0) {
      this.#observers.set(observable, [observer]);
    } else {
      observers.push(observer);
    }
  }
  /**
   * Remove a single observer.
   *
   * @param observable the observable to observe
   * @param observer the observer function
   * @param target if the observer was bound in {@link on} this must match the bound target
   */
  off(observable, observer, target = this.#defaultTarget) {
    if (target) {
      const observers2 = this.#boundObservers.get(observer);
      if (observers2 === void 0) {
        return;
      }
      const bound = observers2.get(target);
      if (bound === void 0) {
        return;
      }
      observers2.delete(target);
      if (observers2.size === 0) {
        this.#boundObservers.delete(observer);
      }
    }
    const observers = this.#observers.get(observable);
    if (observers) {
      const index = observers.indexOf(observer);
      if (index !== -1) {
        observers?.splice(index, 1);
      }
    }
    observable.off(observer);
  }
  /**
   * Checks if there are any observers currently subscribed to the given observable.
   *
   * @param observable the observable to observe
   */
  observes(observable) {
    return this.#observers.get(observable)?.length ?? 0 > 0;
  }
  /**
   * Checks if the given observer is subscribed to the given observable.
   *
   * @param observable the observable to observe
   * @param observer the observer function
   */
  has(observable, observer) {
    return this.#observers.get(observable)?.includes(observer) ?? false;
  }
  /**
   * Remove all observers. The instance can be reused afterward to add new observers.
   */
  close() {
    for (const [observable, observers] of this.#observers.entries()) {
      for (const observer of observers) {
        observable.off(observer);
      }
    }
    this.#observers.clear();
    this.#boundObservers.clear();
  }
  [Symbol.dispose]() {
    this.close();
  }
}
class QuietObservable extends BasicObservable {
  #emitAutomatically = QuietObservable.defaults.emitAutomatically;
  #suppressionEnabled = QuietObservable.defaults.suppressionEnabled;
  #minimumEmitInterval = QuietObservable.defaults.minimumEmitInterval;
  #shouldEmit;
  #source;
  #sink;
  #sourceObserver;
  #sinkObserver;
  #deferredPayload;
  #lastEmitAt;
  #emitTimer;
  constructor(config) {
    super();
    if (config) {
      this.config = config;
    }
  }
  get config() {
    return this;
  }
  set config(config) {
    const { suppressionEnabled, minimumEmitInterval, emitAutomatically } = config;
    if (emitAutomatically !== void 0) {
      this.emitAutomatically = emitAutomatically;
    }
    if (suppressionEnabled !== void 0) {
      this.suppressionEnabled = suppressionEnabled;
    }
    if (minimumEmitInterval !== void 0) {
      this.minimumEmitInterval = minimumEmitInterval;
    }
    if ("shouldEmit" in config) {
      this.shouldEmit = config.shouldEmit;
    }
    if ("source" in config) {
      this.source = config.source;
    }
    if ("sink" in config) {
      this.sink = config.sink;
    }
    if ("handleError" in config) {
      this.handleError = config.handleError ?? defaultErrorHandler;
    }
    if ("handlePromise" in config && config.handlePromise) {
      this.handlePromise = config.handlePromise;
    } else {
      this.isAsync = config.isAsync ?? false;
    }
  }
  get emitAutomatically() {
    return this.#emitAutomatically;
  }
  set emitAutomatically(value) {
    this.#emitAutomatically = value;
    if (value) {
      this.emitSoon();
    } else if (this.#emitTimer) {
      this.#stop();
    }
  }
  get suppressionEnabled() {
    return this.#suppressionEnabled;
  }
  set suppressionEnabled(value) {
    this.#suppressionEnabled = value;
  }
  get minimumEmitInterval() {
    return this.#minimumEmitInterval;
  }
  set minimumEmitInterval(value) {
    if (this.#minimumEmitInterval === value) {
      return;
    }
    const needStart = this.#emitTimer !== void 0;
    if (needStart) {
      this.#stop();
    }
    this.#minimumEmitInterval = value;
    if (needStart) {
      this.#start();
    }
  }
  get source() {
    return this.#source;
  }
  set source(source) {
    if (this.#source === source) {
      return;
    }
    if (this.#source && this.#sourceObserver) {
      this.#source.off(this.#sourceObserver);
    } else if (this.#sourceObserver === void 0) {
      this.#sourceObserver = this.emit.bind(this);
    }
    this.#source = source;
    if (source) {
      source.on(this.#sourceObserver);
    }
  }
  get sink() {
    return this.#sink;
  }
  set sink(sink) {
    if (this.#sink === sink) {
      return;
    }
    if (this.#sink && this.#sinkObserver) {
      this.off(this.#sinkObserver);
    }
    this.#sink = sink;
    if (sink) {
      this.#sinkObserver = (...payload) => sink.emit(...payload);
      this.#sinkObserver[observant] = false;
      this.on(this.#sinkObserver);
    }
  }
  get shouldEmit() {
    return this.#shouldEmit;
  }
  set shouldEmit(shouldEmit) {
    this.#shouldEmit = shouldEmit;
    if (this.#deferredPayload && shouldEmit?.(...this.#deferredPayload) === false) {
      this.#deferredPayload = void 0;
      this.#stop();
    }
  }
  get isObserved() {
    return super.isObserved || this.#sink?.isObserved || false;
  }
  isObservedBy(observer) {
    return this.#sink?.isObservedBy(observer) || this.isObservedBy(observer) || false;
  }
  emit(...payload) {
    const shouldEmit = this.#shouldEmit?.(...payload);
    if (shouldEmit === false) {
      return;
    }
    const immediate = shouldEmit === "now";
    if (!immediate && !this.#emitAutomatically) {
      this.#deferredPayload = payload;
      return;
    }
    const now = import_Time.Time.nowMs;
    if (immediate || !this.#suppressionEnabled || this.#lastEmitAt === void 0 || this.#lastEmitAt + this.#minimumEmitInterval < now) {
      return this.#emit(payload, now);
    }
    if (this.config.skipSuppressedEmits) {
      return;
    }
    this.#deferredPayload = payload;
    this.#start(now);
  }
  /**
   * Emit immediately, regardless of suppression configuration.
   */
  emitNow() {
    this.#stop();
    if (this.#deferredPayload) {
      this.#emit(this.#deferredPayload);
      this.#deferredPayload = void 0;
    }
  }
  /**
   * Emit as soon as allowed by suppression.
   */
  emitSoon() {
    if (this.#deferredPayload && this.#emitTimer === void 0) {
      this.#start();
    }
  }
  [Symbol.dispose]() {
    this.#stop();
  }
  #emit(payload, now) {
    this.#deferredPayload = void 0;
    this.#lastEmitAt = now ?? import_Time.Time.nowMs;
    this.#stop();
    return super.emit(...payload);
  }
  #start(now) {
    if (this.#emitTimer || this.#deferredPayload === void 0) {
      return;
    }
    let timeout;
    if (this.#lastEmitAt === void 0) {
      timeout = import_TimeUnit.Instant;
    } else {
      timeout = (0, import_TimeUnit.Millis)(this.#minimumEmitInterval - ((now ?? import_Time.Time.nowMs) - this.#lastEmitAt));
    }
    if (timeout <= 0) {
      this.emitNow();
    } else {
      this.#emitTimer = import_Time.Time.getTimer("delayed emit", timeout, this.emitNow.bind(this));
      this.#emitTimer.start();
    }
  }
  #stop() {
    if (this.#emitTimer) {
      this.#emitTimer.stop();
      this.#emitTimer = void 0;
    }
  }
}
((QuietObservable2) => {
  QuietObservable2.now = "now";
  QuietObservable2.defaults = {
    emitAutomatically: true,
    suppressionEnabled: true,
    minimumEmitInterval: import_TimeUnit.Seconds.one
  };
})(QuietObservable || (QuietObservable = {}));
//# sourceMappingURL=Observable.js.map
