"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
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 __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();
};
var Environment_exports = {};
__export(Environment_exports, {
  Environment: () => Environment
});
module.exports = __toCommonJS(Environment_exports);
var import_SharedEnvironmentServices = require("#environment/SharedEnvironmentServices.js");
var import_SharedServicesManager = require("#environment/SharedServicesManager.js");
var import_Diagnostic = require("#log/Diagnostic.js");
var import_LogFormat = require("#log/LogFormat.js");
var import_LogLevel = require("#log/LogLevel.js");
var import_MatterError = require("#MatterError.js");
var import_TimeUnit = require("#time/TimeUnit.js");
var import_Lifetime = require("#util/Lifetime.js");
var import_Promises = require("#util/Promises.js");
var import_DiagnosticSource = require("../log/DiagnosticSource.js");
var import_Logger = require("../log/Logger.js");
var import_disposable = require("../polyfills/disposable.js");
var import_Time = require("../time/Time.js");
var import_Lifecycle = require("../util/Lifecycle.js");
var import_Observable = require("../util/Observable.js");
var import_Environmental = require("./Environmental.js");
var import_RuntimeService = require("./RuntimeService.js");
var import_VariableService = require("./VariableService.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_Logger.Logger.get("Environment");
class Environment {
  #services;
  #name;
  #parent;
  #lifetime;
  #added = (0, import_Observable.Observable)();
  #deleted = (0, import_Observable.Observable)();
  #serviceEvents = /* @__PURE__ */ new Map();
  constructor(name, parent) {
    this.#name = name;
    this.#parent = parent;
    this.#lifetime = (parent ?? import_Lifetime.Lifetime.process).join(import_Diagnostic.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 import_SharedEnvironmentServices.SharedEnvironmentServices(this.root);
    this.get(import_SharedServicesManager.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[import_Environmental.Environmental.create]) {
      const instance = type[import_Environmental.Environmental.create](this);
      if (!(instance instanceof type)) {
        throw new import_MatterError.InternalError(`Service creation did not produce instance of ${type.name}`);
      }
      return instance;
    }
    throw new import_Lifecycle.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(import_SharedServicesManager.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(import_SharedServicesManager.SharedServicesManager).has(type)) {
      return void 0;
    }
    return import_Promises.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: (0, import_Observable.Observable)(),
        deleted: (0, import_Observable.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(() => {
      import_Logger.Logger.level = env.vars.get("log.level", import_LogLevel.LogLevel.names[(0, import_LogLevel.LogLevel)(import_Logger.Logger.level)]);
      import_Logger.Logger.format = env.vars.get("log.format", import_Logger.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(import_VariableService.VariableService);
  }
  /**
   * Shortcut for accessing {@link RuntimeService}.
   */
  get runtime() {
    return this.get(import_RuntimeService.RuntimeService);
  }
  /**
   * Display tasks that supply diagnostics.
   */
  diagnose() {
    import_Time.Time.getTimer("diagnostics", import_TimeUnit.Instant, () => {
      try {
        logger.notice("Diagnostics follow", import_DiagnosticSource.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 = () => (import_LogFormat.LogFormat.formats[import_Logger.Logger.format] ?? import_LogFormat.LogFormat.formats.ansi)(import_DiagnosticSource.DiagnosticSource);
}
//# sourceMappingURL=Environment.js.map
