/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Diagnostic } from "#log/Diagnostic.js";
import { Time } from "#time/Time.js";
import { Instant, Millis, Seconds } from "#time/TimeUnit.js";
import { ImplementationError } from "../MatterError.js";
import { Logger } from "../log/Logger.js";
import "../polyfills/disposable.js";
import { asError } from "./Error.js";
import { MaybePromise } from "./Promises.js";
const logger = Logger.get("Observable");
const observant = /* @__PURE__ */ Symbol("consider-observed");
class BasicObservable {
  #isAsync = false;
  #observers;
  #once;
  #instrumentAs;
  #joinIteration;
  #removeIterator;
  #stopIteration;
  constructor(handleError, isAsync) {
    if (handleError) {
      this.handleError = handleError;
    }
    if (isAsync) {
      this.#isAsync = true;
    }
  }
  set traceAs(name) {
    this.#instrumentAs = name;
  }
  [Symbol.dispose]() {
    this.#observers = this.#once = void 0;
    this.#stopIteration?.();
  }
  handleError(error, _observer) {
    throw error;
  }
  set isAsync(isAsync) {
    this.#isAsync = isAsync;
  }
  get isAsync() {
    return this.#isAsync;
  }
  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(
          Diagnostic.strong(this.#instrumentAs),
          `invoking #${nextObserver++}`,
          Diagnostic.strong(observer.name || "(anon)")
        );
      }
      return observer;
    };
    let log, done;
    if (this.#instrumentAs) {
      logger.debug(Diagnostic.strong(this.#instrumentAs), "emitting");
      log = (observer) => {
        logger.debug(
          Diagnostic.strong(this.#instrumentAs),
          `invoking #${nextObserver++}`,
          Diagnostic.strong(observer.name || "(anon)")
        );
      };
      done = () => {
        logger.debug(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(asError(e), observer);
      }
      if (result === void 0) {
        continue;
      }
      if (MaybePromise.is(result)) {
        const emitAsync = async () => {
          while (true) {
            if (MaybePromise.is(result)) {
              try {
                result = await result;
              } catch (e) {
                this.handleError(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(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) {
          break;
        }
        promise = next.promise;
        yield next.value[0];
      }
    } finally {
      this.#removeIterator?.();
    }
  }
  detachObservers() {
    this.#stopIteration?.();
    if (!this.#observers?.size) {
      return;
    }
    const detached = {
      observers: this.#observers,
      once: this.#once
    };
    this.#observers = this.#once = void 0;
    return detached;
  }
  attachObservers(detached) {
    if (!detached.observers) {
      return;
    }
    for (const observer of detached.observers) {
      if (detached.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, promise });
    }
    this.on(observer);
    this.#joinIteration = () => {
      iteratorCount++;
      return promise;
    };
    this.#removeIterator = () => {
      if (!--iteratorCount) {
        this.#stopIteration?.();
      }
    };
    this.#stopIteration = () => {
      this.off(observer);
      resolve(void 0);
      this.#joinIteration = void 0;
      this.#stopIteration = void 0;
      this.#removeIterator = void 0;
    };
    return promise;
  }
}
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 ImplementationError(`Invalid event name ${name}`);
  }
  return observer;
}
class BasicObservableValue extends BasicObservable {
  #value;
  #error;
  #awaiters;
  constructor(value, handleError, isAsync) {
    super(handleError, isAsync);
    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 = 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 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;
    }
    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 = 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 ?? 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 = Instant;
    } else {
      timeout = Millis(this.#minimumEmitInterval - ((now ?? Time.nowMs) - this.#lastEmitAt));
    }
    if (timeout <= 0) {
      this.emitNow();
    } else {
      this.#emitTimer = 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: Seconds.one
  };
})(QuietObservable || (QuietObservable = {}));
export {
  AsyncObservable,
  AsyncObservableValue,
  BasicObservable,
  BasicObservableValue,
  EventEmitter,
  Observable,
  ObservableProxy,
  ObservableValue,
  ObserverGroup,
  QuietObservable,
  observant
};
//# sourceMappingURL=Observable.js.map
