///|
/// The options values of the children of one section, one for each instance
/// name, in configuration order.
pub struct NamedOptions[T] {
  priv instances : Map[String, Options[T]]
}

///|
/// The options value of the instance `name`, or `None` when the section has no
/// child with that name.
pub fn[T] NamedOptions::get(self : NamedOptions[T], name : String) -> Options[T]? {
  self.instances.get(name)
}

///|
/// The instance names, in configuration order.
pub fn[T] NamedOptions::names(self : NamedOptions[T]) -> Array[String] {
  self.instances.keys().collect()
}

///|
/// Bind each child of the section at `key` as one named instance. `bind_one`
/// gets the configuration and the key text of one child, for example
/// `backends.ts`, and gives its options value; `bind`, `bind_or` and
/// `bind_with` with `validated` all fit. Each options value gets the child
/// name as its instance name. The `Decode` and `Validation` failures of all
/// instances raise one `Validation` error. A section that no source sets gives
/// no instances. A section that is not a table raises `Decode`.
pub fn[T] named(
  config : Config,
  key : String,
  bind_one : (Config, String) -> Options[T] raise ConfigError,
) -> NamedOptions[T] raise ConfigError {
  let path = KeyPath::parse(key)
  let instances : Map[String, Options[T]] = Map([])
  guard config.get(path) is Some(section) else { return { instances, } }
  guard section.ion.kind() is Struct(fields) else {
    let at : DecodePath = { key: Some(path), suffix: [] }
    raise Decode(failure={
      at: [{ path: at, blame: config.blame(at) }],
      detail: "must be a table of named instances",
    })
  }
  let failures : Array[BindFailure] = []
  for field in fields {
    guard field.name().text() is Some(name) else { continue }
    let child = KeyPath::from_names([..path.names, name])
    try bind_one(config, child.to_string()) catch {
      Decode(failure~) => failures.push(failure)
      Validation(failures=found) => failures.append(found)
      error => raise error
    } noraise {
      options => instances[name] = { ..options, name: Some(name) }
    }
  }
  if !failures.is_empty() {
    raise Validation(failures~)
  }
  { instances, }
}