const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;
function isObject(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
function cloneDeep(value) {
  if (typeof value !== "object" || value === null) {
    return value;
  }
  if (Array.isArray(value)) {
    return value.map(cloneDeep);
  }
  return Object.fromEntries(
    Object.entries(value).map(([k, v]) => [k, cloneDeep(v)])
  );
}
function merge(into, from) {
  if (into === null) {
    return void 0;
  }
  if (into === void 0) {
    return from === void 0 ? void 0 : merge(from);
  }
  if (typeof into !== "object" || Array.isArray(into)) {
    return into;
  }
  const fromObj = isObject(from) ? from : {};
  const out = {};
  for (const key of /* @__PURE__ */ new Set([...Object.keys(into), ...Object.keys(fromObj)])) {
    const val = merge(into[key], fromObj[key]);
    if (val !== void 0) {
      out[key] = val;
    }
  }
  return out;
}
function typeOf(value) {
  if (value === null) {
    return "null";
  } else if (Array.isArray(value)) {
    return "array";
  }
  const type = typeof value;
  if (type === "number" && isNaN(value)) {
    return "nan";
  }
  if (type === "string" && value === "") {
    return "empty-string";
  }
  return type;
}
const errors = {
  type(key, context, typeName, expected) {
    return `Invalid type in config for key '${key}' in '${context}', got ${typeName}, wanted ${expected}`;
  },
  missing(key, context) {
    return `Missing required config value at '${key}' in '${context}'`;
  },
  convert(key, context, expected) {
    return `Unable to convert config value for key '${key}' in '${context}' to a ${expected}`;
  }
};
class ConfigReader {
  constructor(data, context = "mock-config", fallback, prefix = "") {
    this.data = data;
    this.context = context;
    this.fallback = fallback;
    this.prefix = prefix;
  }
  /**
   * A set of key paths that where removed from the config due to not being visible.
   *
   * This was added as a mutable private member to avoid changes to the public API.
   * Its only purpose of this is to warn users of missing visibility when running
   * the frontend in development mode.
   */
  filteredKeys;
  notifiedFilteredKeys = /* @__PURE__ */ new Set();
  /**
   * Instantiates the config reader from a list of application config objects.
   */
  static fromConfigs(configs) {
    if (configs.length === 0) {
      return new ConfigReader(void 0);
    }
    return configs.reduce(
      (previousReader, { data, context, filteredKeys, deprecatedKeys }) => {
        const reader = new ConfigReader(data, context, previousReader);
        reader.filteredKeys = filteredKeys;
        if (deprecatedKeys) {
          for (const { key, description } of deprecatedKeys) {
            console.warn(
              `The configuration key '${key}' of ${context} is deprecated and may be removed soon. ${description || ""}`
            );
          }
        }
        return reader;
      },
      void 0
    );
  }
  /** {@inheritdoc Config.has} */
  has(key) {
    const value = this.readValue(key);
    if (value === null) {
      return false;
    }
    if (value !== void 0) {
      return true;
    }
    return this.fallback?.has(key) ?? false;
  }
  /** {@inheritdoc Config.keys} */
  keys() {
    const localKeys = this.data ? Object.keys(this.data) : [];
    const fallbackKeys = this.fallback?.keys() ?? [];
    return [.../* @__PURE__ */ new Set([...localKeys, ...fallbackKeys])].filter(
      (k) => this.data?.[k] !== null
    );
  }
  /** {@inheritdoc Config.get} */
  get(key) {
    const value = this.getOptional(key);
    if (value === void 0) {
      throw new Error(errors.missing(this.fullKey(key ?? ""), this.context));
    }
    return value;
  }
  /** {@inheritdoc Config.getOptional} */
  getOptional(key) {
    const value = cloneDeep(this.readValue(key));
    const fallbackValue = this.fallback?.getOptional(key);
    if (value === null) {
      return void 0;
    }
    if (value === void 0) {
      if (process.env.NODE_ENV === "development") {
        if (fallbackValue === void 0 && key) {
          const fullKey = this.fullKey(key);
          if (this.filteredKeys?.includes(fullKey) && !this.notifiedFilteredKeys.has(fullKey)) {
            this.notifiedFilteredKeys.add(fullKey);
            console.warn(
              `Failed to read configuration value at '${fullKey}' as it is not visible. See https://backstage.io/docs/conf/defining#visibility for instructions on how to make it visible.`
            );
          }
        }
      }
      return merge(fallbackValue);
    } else if (fallbackValue === void 0) {
      return merge(value);
    }
    return merge(value, fallbackValue);
  }
  /** {@inheritdoc Config.getConfig} */
  getConfig(key) {
    const value = this.getOptionalConfig(key);
    if (value === void 0) {
      throw new Error(errors.missing(this.fullKey(key), this.context));
    }
    return value;
  }
  /** {@inheritdoc Config.getOptionalConfig} */
  getOptionalConfig(key) {
    const value = this.readValue(key);
    const fallbackConfig = this.fallback?.getOptionalConfig(key);
    if (isObject(value)) {
      return this.copy(value, key, fallbackConfig);
    }
    if (value === null) {
      return void 0;
    }
    if (value !== void 0) {
      throw new TypeError(
        errors.type(this.fullKey(key), this.context, typeOf(value), "object")
      );
    }
    return fallbackConfig;
  }
  /** {@inheritdoc Config.getConfigArray} */
  getConfigArray(key) {
    const value = this.getOptionalConfigArray(key);
    if (value === void 0) {
      throw new Error(errors.missing(this.fullKey(key), this.context));
    }
    return value;
  }
  /** {@inheritdoc Config.getOptionalConfigArray} */
  getOptionalConfigArray(key) {
    const configs = this.readConfigValue(key, (values) => {
      if (!Array.isArray(values)) {
        return { expected: "object-array" };
      }
      for (const [index, value] of values.entries()) {
        if (!isObject(value)) {
          return { expected: "object-array", value, key: `${key}[${index}]` };
        }
      }
      return true;
    });
    if (!configs) {
      if (process.env.NODE_ENV === "development") {
        const fullKey = this.fullKey(key);
        if (this.filteredKeys?.some((k) => k.startsWith(fullKey)) && !this.notifiedFilteredKeys.has(key)) {
          this.notifiedFilteredKeys.add(key);
          console.warn(
            `Failed to read configuration array at '${key}' as it does not have any visible elements. See https://backstage.io/docs/conf/defining#visibility for instructions on how to make it visible.`
          );
        }
      }
      return void 0;
    }
    return configs.map((obj, index) => this.copy(obj, `${key}[${index}]`));
  }
  /** {@inheritdoc Config.getNumber} */
  getNumber(key) {
    const value = this.getOptionalNumber(key);
    if (value === void 0) {
      throw new Error(errors.missing(this.fullKey(key), this.context));
    }
    return value;
  }
  /** {@inheritdoc Config.getOptionalNumber} */
  getOptionalNumber(key) {
    const value = this.readConfigValue(
      key,
      (val) => typeof val === "number" || typeof val === "string" || { expected: "number" }
    );
    if (typeof value === "number" || value === void 0) {
      return value;
    }
    const number = Number(value);
    if (!Number.isFinite(number)) {
      throw new Error(
        errors.convert(this.fullKey(key), this.context, "number")
      );
    }
    return number;
  }
  /** {@inheritdoc Config.getBoolean} */
  getBoolean(key) {
    const value = this.getOptionalBoolean(key);
    if (value === void 0) {
      throw new Error(errors.missing(this.fullKey(key), this.context));
    }
    return value;
  }
  /** {@inheritdoc Config.getOptionalBoolean} */
  getOptionalBoolean(key) {
    const value = this.readConfigValue(
      key,
      (val) => typeof val === "boolean" || typeof val === "number" || typeof val === "string" || { expected: "boolean" }
    );
    if (typeof value === "boolean" || value === void 0) {
      return value;
    }
    const valueString = String(value).trim();
    if (/^(?:y|yes|true|1|on)$/i.test(valueString)) {
      return true;
    }
    if (/^(?:n|no|false|0|off)$/i.test(valueString)) {
      return false;
    }
    throw new Error(errors.convert(this.fullKey(key), this.context, "boolean"));
  }
  /** {@inheritdoc Config.getString} */
  getString(key) {
    const value = this.getOptionalString(key);
    if (value === void 0) {
      throw new Error(errors.missing(this.fullKey(key), this.context));
    }
    return value;
  }
  /** {@inheritdoc Config.getOptionalString} */
  getOptionalString(key) {
    return this.readConfigValue(
      key,
      (value) => typeof value === "string" && value !== "" || { expected: "string" }
    );
  }
  /** {@inheritdoc Config.getStringArray} */
  getStringArray(key) {
    const value = this.getOptionalStringArray(key);
    if (value === void 0) {
      throw new Error(errors.missing(this.fullKey(key), this.context));
    }
    return value;
  }
  /** {@inheritdoc Config.getOptionalStringArray} */
  getOptionalStringArray(key) {
    return this.readConfigValue(key, (values) => {
      if (!Array.isArray(values)) {
        return { expected: "string-array" };
      }
      for (const [index, value] of values.entries()) {
        if (typeof value !== "string" || value === "") {
          return { expected: "string-array", value, key: `${key}[${index}]` };
        }
      }
      return true;
    });
  }
  fullKey(key) {
    return `${this.prefix}${this.prefix ? "." : ""}${key}`;
  }
  copy(data, key, fallback) {
    const reader = new ConfigReader(
      data,
      this.context,
      fallback,
      this.fullKey(key)
    );
    reader.filteredKeys = this.filteredKeys;
    return reader;
  }
  readConfigValue(key, validate) {
    const value = this.readValue(key);
    if (value === void 0) {
      if (process.env.NODE_ENV === "development") {
        const fullKey = this.fullKey(key);
        if (this.filteredKeys?.includes(fullKey) && !this.notifiedFilteredKeys.has(fullKey)) {
          this.notifiedFilteredKeys.add(fullKey);
          console.warn(
            `Failed to read configuration value at '${fullKey}' as it is not visible. See https://backstage.io/docs/conf/defining#visibility for instructions on how to make it visible.`
          );
        }
      }
      return this.fallback?.readConfigValue(key, validate);
    }
    if (value === null) {
      return void 0;
    }
    const result = validate(value);
    if (result !== true) {
      const { key: keyName = key, value: theValue = value, expected } = result;
      throw new TypeError(
        errors.type(
          this.fullKey(keyName),
          this.context,
          typeOf(theValue),
          expected
        )
      );
    }
    return value;
  }
  readValue(key) {
    const parts = key ? key.split(".") : [];
    for (const part of parts) {
      if (!CONFIG_KEY_PART_PATTERN.test(part)) {
        throw new TypeError(`Invalid config key '${key}'`);
      }
    }
    if (this.data === void 0) {
      return void 0;
    }
    let value = this.data;
    for (const [index, part] of parts.entries()) {
      if (isObject(value)) {
        value = value[part];
      } else if (value !== void 0 && value !== null) {
        const badKey = this.fullKey(parts.slice(0, index).join("."));
        throw new TypeError(
          errors.type(badKey, this.context, typeOf(value), "object")
        );
      }
    }
    return value;
  }
}

export { ConfigReader };
//# sourceMappingURL=reader.esm.js.map
