///|
/// A config-loading failure (← go-zero's `conf.Load` errors): malformed JSON or
/// YAML, a non-mapping root, a field of the wrong type, a required field with no
/// value, or a value outside its `options=`/`range=` constraint, with a
/// human-readable reason.
pub suberror ConfigError {
  ConfigError(String)
}

///|
/// Decode a `LogLevel` from a JSON string (`"debug"`/`"info"`/`"error"`/
/// `"severe"`), reusing `LogLevel::parse`'s lenient fallback to `Info`. This impl
/// lets `ServiceConf`'s derived `FromJson` read `log_level` as a plain string —
/// the way go-zero writes it in YAML/JSON config — instead of a tagged variant.
pub impl @json.FromJson for LogLevel with fn from_json(json, path) {
  match json {
    String(s) => LogLevel::parse(s)
    _ => raise @json.JsonDecodeError((path, "log level must be a JSON string"))
  }
}

///|
/// The level names go-zero's `LogConf.Level` accepts (`options=[...]`). A level
/// outside this set is a config error, not a silent fallback.
let log_levels : Array[String] = ["debug", "info", "error", "severe"]

///|
/// Load a `ServiceConf` from a JSON config string, applying go-zero-style
/// defaults for every omitted field (an empty `{}` yields exactly
/// `ServiceConf::new()`). This is the lenient loader mirroring go-zero's
/// `conf.Load` with `,optional`/`,default=` struct tags: unlike the strict
/// derived `FromJson` — reachable via `@json.from_json` and requiring every
/// field present — a partial config is filled from the same defaults `new()`
/// uses.
///
/// Keys are matched canonically, so go-zero's `Name`/`Host`/`Port`/`Timeout` and
/// moonzero's own `timeout_ms`/`log_level` spellings all load.
///
/// Raises `ConfigError` on malformed JSON, a non-object root, a field of the
/// wrong type, or a log level outside `debug|info|error|severe`.
pub fn ServiceConf::from_json(src : String) -> ServiceConf raise ConfigError {
  let def = ServiceConf::new()
  service_conf_of(
    Conf::of_json(src),
    name_default=def.name,
    port_default=def.port,
  )
}

///|
/// Load a `ServiceConf` from a **YAML** config string — the format go-zero
/// actually ships (`etc/*.yaml`) — with the same lenient, default-filling
/// semantics as `from_json`: an empty document yields exactly
/// `ServiceConf::new()`, and each omitted field falls back to its `new()`
/// default. The YAML is parsed by the self-built `yaml_parse` (block mappings,
/// nesting, sequences, scalars, comments) into a `Json` object, then decoded by
/// the shared field reader — so JSON and YAML configs agree field-for-field.
///
/// Raises `ConfigError` on malformed YAML, a non-mapping root, a field of the
/// wrong type, or a log level outside `debug|info|error|severe`.
pub fn ServiceConf::from_yaml(src : String) -> ServiceConf raise ConfigError {
  let def = ServiceConf::new()
  service_conf_of(
    Conf::of_yaml(src),
    name_default=def.name,
    port_default=def.port,
  )
}

///|
/// Decode a `ServiceConf` from a loaded document. Shared by the standalone
/// loaders and by `RestConf`, which differ only in whether `Name` and `Port` have
/// a default: the standalone loaders pass the `new()` values, `RestConf` passes
/// none so a rest service must name itself and its port, as go-zero requires.
///
/// The paths are go-zero's (`Name`, `Host`, `Port`, `Timeout`, `Log.Level`); the
/// alternative spellings are the flat keys moonzero's own configs and examples
/// were written with, kept so those keep loading.
fn service_conf_of(
  c : Conf,
  name_default? : String,
  port_default? : Int,
) -> ServiceConf raise ConfigError {
  let def = ServiceConf::new()
  let name = c.string("Name", default?=name_default, env="MOONZERO_NAME")
  let host = c.string("Host", default=def.host, env="MOONZERO_HOST")
  let port = c.int("Port", default?=port_default, env="MOONZERO_PORT")
  let timeout_ms = c.int(
    "Timeout",
    default=def.timeout_ms,
    env="MOONZERO_TIMEOUT",
    also=["TimeoutMs"],
  )
  let log_level = LogLevel::parse(
    c.string(
      "Log.Level",
      default=def.log_level.to_string(),
      options=log_levels,
      env="MOONZERO_LOG_LEVEL",
      also=["LogLevel"],
    ),
  )
  { name, host, port, timeout_ms, log_level, }
}