///|
fn[T] typed_list(
  config : Value,
  key : String,
  convert : (Value) -> T raise ParseError,
) -> Array[T] raise ParseError {
  let result = []
  for value in collection_values(required(config, key)) {
    result.push(convert(value))
  }
  result
}

///|
// Preserve the former singleton getter's null/unresolved diagnostics and Bare
// conversion, without allocating an object or reparsing its constant path.
fn required_list_value(value : Value) -> Value raise ParseError {
  match required_leaf(value, "value") {
    Bare(text) => Text(text)
    value => value
  }
}

///|
pub fn get_string_list(
  config : Value,
  key : String,
) -> Array[String] raise ParseError {
  typed_list(config, key, value => string_value(required_list_value(value)))
}

///|
pub fn get_bool_list(
  config : Value,
  key : String,
) -> Array[Bool] raise ParseError {
  typed_list(config, key, value => bool_value(required_list_value(value)))
}

///|
pub fn get_int_list(
  config : Value,
  key : String,
) -> Array[Int] raise ParseError {
  typed_list(config, key, value => {
    let n = long_value(number_text(required_list_value(value)))
    if n < -2147483648L || n > 2147483647L {
      raise Invalid("integer out of 32-bit range")
    }
    n.to_int()
  })
}

///|
pub fn get_long_list(
  config : Value,
  key : String,
) -> Array[Int64] raise ParseError {
  typed_list(config, key, value => {
    long_value(number_text(required_list_value(value)))
  })
}

///|
pub fn get_double_list(
  config : Value,
  key : String,
) -> Array[Double] raise ParseError {
  typed_list(config, key, value => number_double(required_list_value(value)))
}

///|
pub fn get_duration_list(
  config : Value,
  key : String,
) -> Array[Int64] raise ParseError {
  typed_list(config, key, value => {
    match required_list_value(value) {
      Number(text) => scale_time(long_value(text), 1000000L)
      value => duration_text(number_text(value))
    }
  })
}

///|
// Collection strings use decimal quantity parsing directly. Unlike scalar
// memory getters, they do not first undergo Java floating-point coercion.
fn collection_memory(value : Value) -> @bigint.BigInt raise ParseError {
  let bytes = match value {
    Number(text) => @bigint.BigInt::from_int64(long_value(text))
    Text(text) => {
      let (number, unit) = quantity(text)
      decimal_scaled(number, byte_multiplier(unit))
    }
    _ => raise Invalid("expected size-compatible list element")
  }
  if bytes.compare_int(0) < 0 {
    raise Invalid("memory size cannot be negative")
  }
  bytes
}

///|
pub fn get_bytes_list(
  config : Value,
  key : String,
) -> Array[Int64] raise ParseError {
  typed_list(config, key, value => {
    let bytes = collection_memory(required_list_value(value))
    if bytes.compare_int64(9223372036854775807L) > 0 {
      raise Invalid("byte count out of int64 range")
    }
    bytes.to_int64()
  })
}

///|
pub fn get_memory_size_list(
  config : Value,
  key : String,
) -> Array[String] raise ParseError {
  typed_list(config, key, value => {
    collection_memory(required_list_value(value)).to_string()
  })
}

///|
pub fn get_config(config : Value, key : String) -> Value raise ParseError {
  let value = peek_document_path(config, key).unwrap_or_else(() => {
    raise Invalid("missing path: " + key)
  })
  if !known_object(value) {
    raise Invalid("expected object-compatible value")
  }
  copy_value(value)
}

///|
pub fn get_config_list(
  config : Value,
  key : String,
) -> Array[Value] raise ParseError {
  typed_list(config, key, value => {
    if !known_object(value) {
      raise Invalid("expected object-compatible value")
    }
    copy_value(value)
  })
}