var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
var __using = (stack, value, async) => {
  if (value != null) {
    if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
    var dispose, inner;
    if (async) dispose = value[__knownSymbol("asyncDispose")];
    if (dispose === void 0) {
      dispose = value[__knownSymbol("dispose")];
      if (async) inner = dispose;
    }
    if (typeof dispose !== "function") __typeError("Object not disposable");
    if (inner) dispose = function() {
      try {
        inner.call(this);
      } catch (e) {
        return Promise.reject(e);
      }
    };
    stack.push([async, dispose, value]);
  } else if (async) {
    stack.push([async]);
  }
  return value;
};
var __callDispose = (stack, error, hasError) => {
  var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
    return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
  };
  var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
  var next = (it) => {
    while (it = stack.pop()) {
      try {
        var result = it[1] && it[1].call(it[2]);
        if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
      } catch (e) {
        fail(e);
      }
    }
    if (hasError) throw error;
  };
  return next();
};
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { DiagnosticPresentation } from "#log/DiagnosticPresentation.js";
import { asError } from "#util/Error.js";
import { Diagnostic } from "../log/Diagnostic.js";
import { DiagnosticSource } from "../log/DiagnosticSource.js";
import { Logger } from "../log/Logger.js";
import { Lifecycle } from "../util/Lifecycle.js";
import { Observable } from "../util/Observable.js";
import { Environmental } from "./Environmental.js";
const logger = Logger.get("Runtime");
class RuntimeService {
  #env;
  #lifetime;
  #workers = /* @__PURE__ */ new Set();
  #cancelled = /* @__PURE__ */ new Set();
  #workerDeleted = Observable();
  #canceled = false;
  #started = Observable();
  #stopped = Observable();
  #crashed = Observable();
  constructor(environment) {
    this.#env = environment;
    this.#lifetime = this.#env.join("runtime");
    environment.set(RuntimeService, this);
    DiagnosticSource.add(this);
  }
  /**
   * Add a {@link Worker}, either directly or by invoking an {@link Initiator}.
   *
   * The runtime considers itself "active" if there are one or more workers installed.
   *
   * A worker must either be {@link PromiseLike} or {@link Constructable} for the runtime to detect completion. On
   * completion the worker is removed and destroyed if the worker is {@link Destructable}.
   *
   * Once added, the {@link worker} is owned by the RuntimeService until closed, resolved or removed via
   * {@link delete}.
   */
  add(worker) {
    if (!worker) {
      return;
    }
    if (typeof worker === "function") {
      try {
        this.add(worker(this.#env));
      } catch (e) {
        this.#crash(asError(e));
      }
      return;
    }
    if (this.#workers.has(worker)) {
      return;
    }
    this.#workers.add(worker);
    if (this.#workers.size === 1) {
      this.#started.emit();
    }
    if (worker.then) {
      Promise.resolve(worker).catch((error) => this.#crash(error)).finally(() => {
        this.delete(worker);
      });
      return;
    }
    if (worker.construction?.change) {
      worker.construction.change.on((status) => {
        switch (status) {
          case Lifecycle.Status.Crashed:
            this.#crash();
            break;
          case Lifecycle.Status.Destroyed:
            this.delete(worker);
            break;
        }
      });
    }
  }
  /**
   * Remove a worker.
   */
  delete(worker) {
    if (!this.#workers.has(worker)) {
      return;
    }
    this.#workers.delete(worker);
    this.#cancelled.delete(worker);
    this.#workerDeleted.emit();
    if (this.#workers.size) {
      return;
    }
    this.cancel();
    void this.inactive.finally(() => this.#stopped.emit());
  }
  /**
   * Emits when a worker is added when previously there were none.
   */
  get started() {
    return this.#started;
  }
  /**
   * Emits when the last worker departs.
   */
  get stopped() {
    return this.#stopped;
  }
  /**
   * Emits when a worker experiences an unhandled error.
   */
  get crashed() {
    return this.#crashed;
  }
  static [Environmental.create](environment) {
    return new this(environment);
  }
  /**
   * Cancel execution.
   *
   * On cancel the runtime destroys all workers.
   */
  cancel() {
    if (this.#canceled) {
      return;
    }
    this.#canceled = true;
    logger.notice("Shutting down");
    for (const worker of this.#workers) {
      const disposal = this.#cancelWorker(worker);
      if (disposal) {
        this.add(disposal);
      }
    }
  }
  /**
   * Interrupt handler.  Triggered by e.g. SIGINT on unixish systems.
   *
   * The default implementation cancels the runtime.
   */
  interrupt() {
    this.cancel();
    if (typeof MatterHooks !== "undefined") {
      MatterHooks.interrupt();
    }
  }
  /**
   * Resolves when no workers are active.
   */
  get inactive() {
    if (!this.#workers.size) {
      return Promise.resolve();
    }
    return new Promise((resolve) => {
      const listener = () => {
        if (!this.#workers.size) {
          this.#workerDeleted.off(listener);
          resolve();
        }
      };
      this.#workerDeleted.on(listener);
    });
  }
  async close() {
    var _stack = [];
    try {
      const _closing = __using(_stack, this.#lifetime.closing());
      this.cancel();
      await this.inactive;
      this.#env.delete(RuntimeService, this);
      DiagnosticSource.delete(this);
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  [Symbol.asyncDispose]() {
    return this.close();
  }
  get [Diagnostic.value]() {
    return Diagnostic.node("\u{1F6E0}", `workers for ${this.#env.name}`, {
      children: [...this.#workers].map((worker) => {
        let diagnostic = worker[DiagnosticPresentation.name];
        if (diagnostic === void 0) {
          diagnostic = Diagnostic.valueOf(worker);
          if (diagnostic === void 0) {
            diagnostic = worker.toString();
          }
        }
        return diagnostic;
      })
    });
  }
  #cancelWorker(worker) {
    if (this.#cancelled.has(worker)) {
      return;
    }
    const cancel = () => {
      this.#cancelled.add(worker);
      try {
        if (worker.close) {
          return Promise.resolve(worker.close()).catch(unhandled).finally(() => this.delete(worker));
        }
        if (worker[Symbol.asyncDispose]) {
          return Promise.resolve(worker[Symbol.asyncDispose]?.()).catch(unhandled).finally(() => this.delete(worker));
        }
        if (worker[Symbol.dispose]) {
          worker[Symbol.dispose]?.();
          this.delete(worker);
          return;
        }
      } catch (e) {
        unhandled(e);
        this.delete(worker);
      }
    };
    if (worker.construction) {
      worker.construction.onSuccess(cancel);
      return;
    }
    return cancel();
    function unhandled(e) {
      logger.error(`Unhandled error closing worker:`, e);
    }
  }
  #crash(cause) {
    if (cause) {
      logger.error(cause);
    }
    this.crashed.emit(cause);
    this.cancel();
  }
}
export {
  RuntimeService
};
//# sourceMappingURL=RuntimeService.js.map
