///|
fn argument_text(
  arguments : Map[String, Argument],
  name : String,
) -> String raise MessageError {
  match arguments.get(name) {
    Some(Text(value)) => value
    Some(Number(value)) => value.to_string()
    None => raise MissingArgument(name)
  }
}

///|
fn argument_number(
  arguments : Map[String, Argument],
  name : String,
) -> Int raise MessageError {
  match arguments.get(name) {
    Some(Number(value)) => value
    Some(Text(_)) => raise WrongArgumentType(name)
    None => raise MissingArgument(name)
  }
}

///|
fn format_nodes(
  nodes : Array[MessageNode],
  arguments : Map[String, Argument],
  locale : String,
  pound : Int?,
) -> String raise MessageError {
  let out = StringBuilder::new()
  for node in nodes {
    match node {
      TextNode(text) => out.write_string(text)
      ArgumentNode(name) => out.write_string(argument_text(arguments, name))
      PoundNode =>
        match pound {
          Some(value) => out.write_string(value.to_string())
          None => out.write_char('#')
        }
      ChoiceNode(name, kind, cases) => {
        let selector = if kind == "select" {
          argument_text(arguments, name)
        } else {
          let value = argument_number(arguments, name)
          let exact = "=" + value.to_string()
          if cases.contains(exact) {
            exact
          } else if kind == "selectordinal" {
            ordinal_category(locale, value)
          } else {
            plural_category(locale, value)
          }
        }
        let branch = match cases.get(selector) {
          Some(value) => value
          None =>
            match cases.get("other") {
              Some(value) => value
              None => raise MissingChoice(selector)
            }
        }
        let nested_pound = if kind == "select" {
          pound
        } else {
          Some(argument_number(arguments, name))
        }
        out.write_string(format_nodes(branch, arguments, locale, nested_pound))
      }
    }
  }
  out.to_string()
}

///|
/// Format a parsed message with text and integer arguments.
pub fn format(
  message : Message,
  arguments : Map[String, Argument],
  locale : String,
) -> String raise MessageError {
  format_nodes(message.nodes, arguments, locale, None)
}