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 { SharedEnvironmentServices } from "#environment/SharedEnvironmentServices.js";
import { SharedServicesManager } from "#environment/SharedServicesManager.js";
import { Diagnostic } from "#log/Diagnostic.js";
import { LogFormat } from "#log/LogFormat.js";
import { LogLevel } from "#log/LogLevel.js";
import { InternalError } from "#MatterError.js";
import { Instant } from "#time/TimeUnit.js";
import { Lifetime } from "#util/Lifetime.js";
import { MaybePromise } from "#util/Promises.js";
import { DiagnosticSource } from "../log/DiagnosticSource.js";
import { Logger } from "../log/Logger.js";
import "../polyfills/disposable.js";
import { Time } from "../time/Time.js";
import { UnsupportedDependencyError } from "../util/Lifecycle.js";
import { Observable } from "../util/Observable.js";
import { Environmental } from "./Environmental.js";
import { RuntimeService } from "./RuntimeService.js";
import { VariableService } from "./VariableService.js";
const logger = Logger.get("Environment");
class Environment {
  #services;
  #name;
  #parent;
  #lifetime;
  #added = Observable();
  #deleted = Observable();
  #serviceEvents = /* @__PURE__ */ new Map();
  constructor(name, parent) {
    this.#name = name;
    this.#parent = parent;
    this.#lifetime = (parent ?? Lifetime.process).join(Diagnostic.strong(name), "environment");
  }
  join(...name) {
    return this.#lifetime?.join(...name);
  }
  /**
   * Create a shared service instance for reference-counted service access at root.
   *
   * Shared instances track which services they use and enable safe sharing across multiple
   * consumers. Services are automatically protected from premature closure and only
   * cleaned up when all consumers have released them, e.g. by calling close().
   *
   * All shared instances operate at the root environment for centralized lifecycle
   * management, regardless of which environment in the hierarchy creates them.
   *
   * @returns A new shared service instance for accessing services with lifecycle tracking
   */
  asDependent() {
    const dependent = new SharedEnvironmentServices(this.root);
    this.get(SharedServicesManager).add(dependent);
    return dependent;
  }
  /**
   * Determine if an environmental service is available.
   */
  has(type) {
    const instance = this.#services?.get(type);
    if (instance === null) {
      return false;
    }
    return instance !== void 0 || (this.#parent?.has(type) ?? false);
  }
  /**
   * Determine if an environmental services is owned by this environment (not an ancestor).
   */
  owns(type) {
    return !!this.#services?.get(type);
  }
  /**
   * Access an environmental service.
   */
  get(type) {
    const mine = this.#services?.get(type);
    if (mine !== void 0 && mine !== null) {
      return mine;
    }
    if (mine === void 0) {
      const instance = this.#parent?.maybeGet(type);
      if (instance !== void 0 && instance !== null) {
        return instance;
      }
    }
    if (type[Environmental.create]) {
      const instance = type[Environmental.create](this);
      if (!(instance instanceof type)) {
        throw new InternalError(`Service creation did not produce instance of ${type.name}`);
      }
      return instance;
    }
    throw new UnsupportedDependencyError(`Required dependency ${type.name}`, "is not available");
  }
  /**
   * Access an environmental service that may not exist.
   */
  maybeGet(type) {
    if (this.has(type)) {
      return this.get(type);
    }
  }
  /**
   * Remove an environmental service and block further inheritance.
   *
   * If any consumer is currently using the service, deletion is deferred until all
   * consumers have released it. Services accessed via {@link SharedEnvironmentServices}
   * are protected from premature removal.
   */
  delete(type, instance) {
    const localInstance = this.#services?.get(type);
    if (this.get(SharedServicesManager).has(type)) {
      return;
    }
    this.#services?.set(type, null);
    if (localInstance === void 0 || localInstance === null) {
      return;
    }
    if (instance !== void 0 && localInstance !== instance) {
      return;
    }
    this.#deleted.emit(type, localInstance);
    const serviceEvents = this.#serviceEvents.get(type);
    if (serviceEvents) {
      serviceEvents.deleted.emit(localInstance);
    }
  }
  /**
   * Remove and close an environmental service.
   *
   * Calls the service's close method if present, then removes it from the environment.
   * If any consumer is currently using the service, closure is deferred until all
   * consumers have released it.
   */
  close(type) {
    const instance = this.maybeGet(type);
    if (instance === void 0) {
      this.delete(type, instance);
      return void 0;
    }
    if (this.get(SharedServicesManager).has(type)) {
      return void 0;
    }
    return MaybePromise.finally(
      () => instance.close?.(),
      () => this.delete(type, instance)
    );
  }
  /**
   * Access an environmental service, waiting for any async initialization to complete.
   */
  async load(type) {
    const instance = this.get(type);
    await instance.construction;
    return instance;
  }
  /**
   * Install a preinitialized version of an environmental service.
   */
  set(type, instance) {
    if (!this.#services) {
      this.#services = /* @__PURE__ */ new Map();
    }
    this.#services.set(type, instance);
    this.#added.emit(type, instance);
    const serviceEvents = this.#serviceEvents.get(type);
    if (serviceEvents) {
      serviceEvents.added.emit(instance);
    }
  }
  /**
   * Name of the environment.
   */
  get name() {
    return this.#name;
  }
  /**
   * Get the root environment in the hierarchy.
   *
   * Recursively traverses parent links to find the topmost environment with no parent.
   * Used internally to ensure certain services (like DependentsManager) are installed
   * at a single, consistent location for the entire environment tree.
   */
  get root() {
    return this.#parent?.root ?? this;
  }
  /**
   * Emits on service add.
   *
   * Currently only emits for services owned directly by this environment.
   */
  get added() {
    return this.#added;
  }
  /**
   * Emits on service delete.
   *
   * Currently only emits for services owned directly by this environment.
   */
  get deleted() {
    return this.#deleted;
  }
  /**
   * Obtain an object with events that trigger when a specific service is added or deleted.
   *
   * This is a more convenient way to observe a specific service than {@link added} and {@link deleted}.
   */
  eventsFor(type) {
    let events = this.#serviceEvents.get(type);
    if (events === void 0) {
      events = {
        added: Observable(),
        deleted: Observable()
      };
      this.#serviceEvents.set(type, events);
    }
    return events;
  }
  /**
   * Apply functions to a specific service type automatically.
   *
   * The environment invokes {@link added} immediately if the service is currently present.  It then invokes
   * {@link added} in the future if the service is added or replaced and/or {@link deleted} if the service is replaced
   * or deleted.
   */
  applyTo(type, added, deleted) {
    const events = this.eventsFor(type);
    if (added) {
      const existing = this.maybeGet(type);
      if (existing) {
        added(this, existing);
      }
      events.added.on(
        (service) => this.runtime.add(async () => {
          var _stack = [];
          try {
            const _adding = __using(_stack, this.join(`adding ${type.name}`));
            await added(this, service);
          } catch (_) {
            var _error = _, _hasError = true;
          } finally {
            __callDispose(_stack, _error, _hasError);
          }
        })
      );
    }
    if (deleted) {
      events.deleted.on(
        (service) => this.runtime.add(async () => {
          var _stack = [];
          try {
            const _deleting = __using(_stack, this.join(`adding ${type.name}`));
            await deleted(this, service);
          } catch (_) {
            var _error = _, _hasError = true;
          } finally {
            __callDispose(_stack, _error, _hasError);
          }
        })
      );
    }
  }
  /**
   * The default environment.
   *
   * Currently only emits for services owned directly by this environment.
   */
  static get default() {
    return global;
  }
  /**
   * Set the default environment.
   */
  static set default(env) {
    global[Symbol.dispose]();
    global = env;
    env.vars.use(() => {
      Logger.level = env.vars.get("log.level", LogLevel.names[LogLevel(Logger.level)]);
      Logger.format = env.vars.get("log.format", Logger.format);
      const stackLimit = global.vars.number("log.stack.limit");
      if (stackLimit !== void 0) {
        Error.stackTraceLimit = stackLimit;
      }
    });
  }
  /**
   * Shortcut for accessing {@link VariableService.vars}.
   */
  get vars() {
    return this.get(VariableService);
  }
  /**
   * Shortcut for accessing {@link RuntimeService}.
   */
  get runtime() {
    return this.get(RuntimeService);
  }
  /**
   * Display tasks that supply diagnostics.
   */
  diagnose() {
    Time.getTimer("diagnostics", Instant, () => {
      try {
        logger.notice("Diagnostics follow", DiagnosticSource);
      } catch (e) {
        logger.error(`Unhandled error gathering diagnostics:`, e);
      }
    }).start();
  }
  loadVariables() {
    return {};
  }
  [Symbol.dispose]() {
    this.#lifetime[Symbol.dispose]();
  }
}
let global = new Environment("default");
if (typeof MatterHooks !== "undefined") {
  MatterHooks.generateDiagnostics = () => (LogFormat.formats[Logger.format] ?? LogFormat.formats.ansi)(DiagnosticSource);
}
export {
  Environment
};
//# sourceMappingURL=Environment.js.map
