///|
/// `when` activates the entire rule on equality with a declared input field.
fn parse_consumer_condition(
  source : ConfigValue,
) -> Result[(ConfigPath, ConfigValue)?, String] {
  let value = match source.field("when") {
    None => return Ok(None)
    Some(value) if value.kind() == Object => value
    _ => return Err("consumer 'when' must be an object")
  }
  let path = match value.field("path") {
    Some(path) =>
      match path.as_string() {
        Some(text) =>
          match parse_path(text) {
            Ok(path) => path
            Err(_) => return Err("invalid consumer condition path")
          }
        None => return Err("consumer condition 'path' must be a string")
      }
    None => return Err("consumer condition is missing 'path'")
  }
  let expected = match value.field("equals") {
    Some(expected) => expected
    None => return Err("consumer condition is missing 'equals'")
  }
  let fields = match value.raw {
    Object(fields) => fields
    _ => abort("unreachable")
  }
  for key in fields.keys() {
    if key != "path" && key != "equals" {
      return Err("unknown consumer condition field '" + key + "'")
    }
  }
  Ok(Some((path, expected)))
}

///|
fn consumer_unconditional_rules(
  rules : Array[ConfigConsumerRule],
) -> Map[String, ConfigConsumerRule] {
  let inputs : Map[String, ConfigConsumerRule] = Map([])
  for rule in rules {
    if rule.when is None {
      inputs[rule.path.to_string()] = rule
    }
  }
  inputs
}

///|
/// Input rules must be unconditional, avoiding cycles and ambiguous defaults.
fn validate_consumer_conditions(
  rules : Array[ConfigConsumerRule],
) -> Result[Unit, String] {
  let inputs = consumer_unconditional_rules(rules)
  for rule in rules {
    match rule.when {
      None => ()
      Some((path, expected)) => {
        let input = match inputs.get(path.to_string()) {
          Some(input) => input
          None =>
            return Err(
              "consumer condition must reference an unconditional rule at '" +
              path.to_string() +
              "'",
            )
        }
        if consumer_violation(input, expected) is Some(_) {
          return Err(
            "consumer condition value violates its input rule at '" +
            path.to_string() +
            "'",
          )
        }
      }
    }
  }
  Ok(())
}

///|
fn consumer_condition_matches(
  rule : ConfigConsumerRule,
  candidate : ConfigValue,
  inputs : Map[String, ConfigConsumerRule],
) -> Bool {
  match rule.when {
    None => true
    Some((path, expected)) => {
      let input = inputs.get(path.to_string()).unwrap()
      let actual = match candidate.get(path) {
        Some(value) => Some(value)
        None => input.default_value
      }
      match actual {
        Some(value) => value == expected
        None => false
      }
    }
  }
}