import { InputError } from '@backstage/errors';

const propsOfHumanDuration = [
  "years",
  "months",
  "weeks",
  "days",
  "hours",
  "minutes",
  "seconds",
  "milliseconds"
];
function readDurationFromConfig(config, options) {
  let root;
  let found = false;
  const result = {};
  try {
    root = (options == null ? void 0 : options.key) ? config.getConfig(options.key) : config;
    for (const prop of propsOfHumanDuration) {
      const value = root.getOptionalNumber(prop);
      if (value !== void 0) {
        result[prop] = value;
        found = true;
      }
    }
  } catch (error) {
    throw new InputError(`Failed to read duration from config, ${error}`);
  }
  try {
    if (!found) {
      const good = propsOfHumanDuration.map((p) => `'${p}'`).join(", ");
      throw new Error(`Needs one or more of ${good}`);
    }
    const invalidProps = root.keys().filter((prop) => !propsOfHumanDuration.includes(prop));
    if (invalidProps.length) {
      const what = invalidProps.length === 1 ? "property" : "properties";
      const bad = invalidProps.map((p) => `'${p}'`).join(", ");
      const good = propsOfHumanDuration.map((p) => `'${p}'`).join(", ");
      throw new Error(
        `Unknown ${what} ${bad}; expected one or more of ${good}`
      );
    }
  } catch (error) {
    let prefix = "Failed to read duration from config";
    if (options == null ? void 0 : options.key) {
      prefix += ` at '${options.key}'`;
    }
    throw new InputError(`${prefix}, ${error}`);
  }
  return result;
}

var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  return value;
};
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.
     */
    __publicField(this, "filteredKeys");
    __publicField(this, "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) {
    var _a, _b;
    const value = this.readValue(key);
    if (value === null) {
      return false;
    }
    if (value !== void 0) {
      return true;
    }
    return (_b = (_a = this.fallback) == null ? void 0 : _a.has(key)) != null ? _b : false;
  }
  /** {@inheritdoc Config.keys} */
  keys() {
    var _a, _b;
    const localKeys = this.data ? Object.keys(this.data) : [];
    const fallbackKeys = (_b = (_a = this.fallback) == null ? void 0 : _a.keys()) != null ? _b : [];
    return [.../* @__PURE__ */ new Set([...localKeys, ...fallbackKeys])].filter(
      (k) => {
        var _a2;
        return ((_a2 = this.data) == null ? void 0 : _a2[k]) !== null;
      }
    );
  }
  /** {@inheritdoc Config.get} */
  get(key) {
    const value = this.getOptional(key);
    if (value === void 0) {
      throw new Error(errors.missing(this.fullKey(key != null ? key : ""), this.context));
    }
    return value;
  }
  /** {@inheritdoc Config.getOptional} */
  getOptional(key) {
    var _a, _b;
    const value = cloneDeep(this.readValue(key));
    const fallbackValue = (_a = this.fallback) == null ? void 0 : _a.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 (((_b = this.filteredKeys) == null ? void 0 : _b.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) {
    var _a;
    const value = this.readValue(key);
    const fallbackConfig = (_a = this.fallback) == null ? void 0 : _a.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) {
    var _a;
    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 (((_a = this.filteredKeys) == null ? void 0 : _a.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) {
    var _a, _b;
    const value = this.readValue(key);
    if (value === void 0) {
      if (process.env.NODE_ENV === "development") {
        const fullKey = this.fullKey(key);
        if (((_a = this.filteredKeys) == null ? void 0 : _a.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 (_b = this.fallback) == null ? void 0 : _b.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, readDurationFromConfig };
//# sourceMappingURL=index.esm.js.map
