/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { ImplementationError } from "../MatterError.js";
import { Diagnostic } from "../log/Diagnostic.js";
import { isObject } from "../util/Type.js";
import { Environmental } from "./Environmental.js";
class VariableService {
  #vars = {};
  #usageCollectors = Array();
  #usages = /* @__PURE__ */ new Set();
  constructor(environment) {
    environment.set(VariableService, this);
  }
  static [Environmental.create](environment) {
    return new this(environment);
  }
  get [Diagnostic.value]() {
    return "Configuration";
  }
  get vars() {
    return this.#vars;
  }
  /**
   * Configure a component.
   *
   * Runs the supplied {@link configurator} immediately and whenever variables reference by the {@link configurator}
   * change.
   *
   * To terminate reconfiguration invoke {@link VariableService.Usage.close} on the return value.
   */
  use(configurator) {
    const variables = /* @__PURE__ */ new Set();
    try {
      this.#usageCollectors.push(variables);
      configurator();
    } finally {
      this.#usageCollectors.pop();
    }
    const usage = {
      variables,
      configurator,
      close: () => {
        this.#usages.delete(usage);
      }
    };
    this.#usages.add(usage);
    return usage;
  }
  get(name, fallback) {
    for (const collector of this.#usageCollectors) {
      collector.add(name);
    }
    switch (typeof fallback) {
      case "string":
        return this.string(name) ?? fallback;
      case "number":
        return this.number(name) ?? fallback;
      case "boolean":
        return this.boolean(name) ?? fallback;
    }
    let value = this.#vars;
    for (const segment of this.#parseName(name)) {
      if (value === null || typeof value !== "object" || Array.isArray(value)) {
        return fallback;
      }
      value = value[segment];
    }
    return value ?? fallback;
  }
  has(name) {
    return this.get(name) !== void 0;
  }
  set(name, value) {
    const segments = name.toLowerCase().split(".");
    const key = segments.pop();
    if (key === "__proto__" || key === "constructor" || key === "prototype") {
      throw new ImplementationError(`Invalid variable path name: ${key}`);
    }
    let parent = this.#vars;
    for (const segment of segments) {
      let nextParent = parent[segment];
      if (nextParent === void 0 || nextParent === null) {
        nextParent = parent[segment] = {};
      } else if (typeof nextParent !== "object" || Array.isArray(nextParent)) {
        throw new ImplementationError(`Can't set ${name} because segment ${segment} is not a map`);
      }
      parent = nextParent;
    }
    if (parent[key] === value) {
      return;
    }
    parent[key] = value;
    for (const usage of this.#usages) {
      if (usage.variables.has(name)) {
        usage.configurator();
      }
    }
  }
  async persist(name, value) {
    this.set(name, value);
    await this.persistConfigValue?.(name, value);
  }
  persistConfigValue;
  string(name) {
    const value = this.get(name);
    if (value === void 0) {
      return value;
    }
    if (typeof value === "string") {
      return value;
    }
    if (value === null) {
      return "null";
    }
    return value.toString();
  }
  boolean(name) {
    const value = this.get(name);
    switch (value) {
      case void 0:
        return value;
      case null:
      case 0:
      case "0":
      case false:
      case "false":
      case "off":
        return false;
      default:
        return true;
    }
  }
  number(name) {
    let value = this.get(name);
    if (typeof value === "number") {
      return value;
    }
    if (typeof value === "string") {
      value = Number.parseFloat(value);
      if (!Number.isFinite(value)) {
        return;
      }
      return value;
    }
  }
  integer(name, fallback) {
    const number = this.number(name) ?? fallback;
    if (typeof number === "number") {
      return Math.floor(number);
    }
  }
  increment(name) {
    const value = this.integer(name) ?? 0;
    this.set(name, value + 1);
    return value;
  }
  addConfigStyle(vars) {
    const path = Array();
    const add = (vars2) => {
      for (const name in vars2) {
        const val = vars2[name];
        if (isObject(val)) {
          path.push(name);
          add(val);
          path.pop();
        } else {
          this.set([...path, name].join("."), val);
        }
      }
    };
    add(vars);
  }
  addUnixEnvStyle(vars) {
    this.addConfigStyle(parseUnixStyle(vars));
  }
  addArgvStyle(vars) {
    this.addConfigStyle(parseArgvStyle(vars));
  }
  #parseName(name) {
    return name.toLowerCase().split(".").map((segment) => {
      segment = segment.trim();
      if (segment === "") {
        throw new ImplementationError(`Variable name ${name} contains empty segments`);
      }
      return segment;
    });
  }
}
function addVariable(into, path, value) {
  if (!path.length) {
    return;
  }
  const firstPathEntry = path[0];
  if (firstPathEntry === "__proto__" || firstPathEntry === "constructor" || firstPathEntry === "prototype") {
    throw new ImplementationError(`Invalid variable path name: ${firstPathEntry}`);
  }
  let current = into[firstPathEntry];
  if (path.length === 1) {
    if (current === void 0) {
      into[firstPathEntry] = value;
    }
    return;
  }
  if (typeof current !== "object") {
    current = into[firstPathEntry] = {};
  }
  addVariable(current, path.slice(1), value);
}
function parseUnixStyle(values) {
  const variables = {};
  for (const key in values) {
    if (key.startsWith("MATTER_")) {
      if (values[key] === void 0 || values[key] === "") {
        continue;
      }
      addVariable(variables, key.slice(7).toLowerCase().split("_"), values[key]);
    }
  }
  return variables;
}
function parseArgvStyle(values) {
  const variables = {};
  for (let arg of values) {
    if (!arg.startsWith("--")) {
      continue;
    }
    arg = arg.slice(2);
    const separatorPos = arg.indexOf("=");
    let key, value;
    if (separatorPos === -1) {
      key = arg;
      value = true;
    } else {
      key = arg.slice(0, separatorPos);
      value = arg.slice(separatorPos + 1);
      if (value.startsWith("'") && value.endsWith("'") || value.startsWith('"') && value.endsWith('"')) {
        value = value.slice(1, -1).replaceAll("\\'", "'").replaceAll('\\"', '"');
      }
    }
    addVariable(variables, key.toLowerCase().split("-"), value);
  }
  return variables;
}
export {
  VariableService
};
//# sourceMappingURL=VariableService.js.map
