///|
fn selected_node(
  doc : Document,
  section : String,
  key : String,
) -> Result[Node, Diagnostic] {
  let entries = doc.entries(section, key)
  if entries.is_empty() {
    return Err(edit_problem("VALUE001", "key is missing"))
  }
  if entries.length() > 1 && doc.dialect.duplicates == Reject {
    return Err(
      diagnostic(
        "VALUE002",
        "key has ambiguous duplicates",
        entries[1].key_span,
      ),
    )
  }
  let node = if doc.dialect.duplicates == LastWins {
    entries[entries.length() - 1]
  } else {
    entries[0]
  }
  if doc.diagnostics.any(d => {
      d.severity == Error &&
      d.span.start >= node.span.start &&
      d.span.start < node.span.end
    }) {
    return Err(
      diagnostic("VALUE003", "value has syntax errors", node.value_span),
    )
  }
  Ok(node)
}

///|
/// Decimal signed 32-bit integer only; overflow is checked before conversion.
fn decimal_int(value : String) -> Int? {
  if value.length() == 0 {
    return None
  }
  let negative = value[0] == '-'
  let start = if negative || value[0] == '+' { 1 } else { 0 }
  if start == value.length() {
    return None
  }
  let mut result : Int64 = 0
  let limit : Int64 = if negative { 2147483648 } else { 2147483647 }
  for i = start; i < value.length(); i = i + 1 {
    if value[i] < '0' || value[i] > '9' {
      return None
    }
    let digit = value[i].to_int() - 48
    result = result * 10 + Int64::from_int(digit)
    if result > limit {
      return None
    }
  }
  Some((if negative { -result } else { result }).to_int())
}

///|
fn boolean(value : String) -> Bool? {
  match value.to_lower() {
    "true" | "yes" | "on" | "1" => Some(true)
    "false" | "no" | "off" | "0" => Some(false)
    _ => None
  }
}

///|
pub fn Document::get_int(
  self : Document,
  section : String,
  key : String,
) -> Result[Int, Diagnostic] {
  match selected_node(self, section, key) {
    Err(e) => Err(e)
    Ok(n) =>
      match decimal_int(n.value) {
        Some(v) => Ok(v)
        None =>
          Err(
            diagnostic(
              "VALUE004",
              "expected decimal signed 32-bit integer",
              n.value_span,
            ),
          )
      }
  }
}

///|
pub fn Document::get_bool(
  self : Document,
  section : String,
  key : String,
) -> Result[Bool, Diagnostic] {
  match selected_node(self, section, key) {
    Err(e) => Err(e)
    Ok(n) =>
      match boolean(n.value) {
        Some(v) => Ok(v)
        None =>
          Err(
            diagnostic(
              "VALUE005",
              "expected true/false, yes/no, on/off or 1/0",
              n.value_span,
            ),
          )
      }
  }
}