///|
pub fn FormatOptions::default() -> FormatOptions {
  { missing_argument: RaiseError, maximum_depth: 100 }
}

///|
pub fn FormatOptions::permissive() -> FormatOptions {
  { missing_argument: KeepPlaceholder, maximum_depth: 100 }
}

///|
pub fn FormatOptions::missing_argument(
  self : FormatOptions,
) -> MissingArgumentBehavior {
  self.missing_argument
}

///|
pub fn FormatOptions::maximum_depth(self : FormatOptions) -> Int {
  self.maximum_depth
}

///|
pub fn MissingArgumentBehavior::name(self : MissingArgumentBehavior) -> String {
  match self {
    RaiseError => "error"
    KeepPlaceholder => "placeholder"
    ReplaceWithEmpty => "empty"
  }
}

///|
pub fn Argument::kind(self : Argument) -> String {
  match self {
    Text(_) => "text"
    Number(_) => "number"
  }
}

///|
pub fn Argument::as_text(self : Argument) -> String {
  match self {
    Text(value) => value
    Number(value) => value.to_string()
  }
}

///|
pub fn Argument::as_number(self : Argument) -> Int raise MessageError {
  match self {
    Number(value) => value
    Text(_) => raise WrongArgumentType("value")
  }
}

///|
pub fn Argument::is_text(self : Argument) -> Bool {
  self is Text(_)
}

///|
pub fn Argument::is_number(self : Argument) -> Bool {
  self is Number(_)
}

///|
fn configurable_argument_text(
  arguments : Map[String, Argument],
  name : String,
  behavior : MissingArgumentBehavior,
) -> String raise MessageError {
  match arguments.get(name) {
    Some(value) => value.as_text()
    None =>
      match behavior {
        RaiseError => raise MissingArgument(name)
        KeepPlaceholder => "{" + name + "}"
        ReplaceWithEmpty => ""
      }
  }
}

///|
fn required_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_event(
  kind : FormatEventKind,
  depth : Int,
  path : String,
  output : String,
  argument? : String,
  selector? : String,
) -> FormatEvent {
  { kind, depth, path, argument, selector, output }
}

///|
fn child_path(path : String, index : Int) -> String {
  if path == "" {
    index.to_string()
  } else {
    path + "/" + index.to_string()
  }
}

///|
fn selectors_for_cases(
  cases : Map[String, Array[MessageNode]],
) -> Array[String] {
  let selectors : Array[String] = []
  for selector, _ in cases {
    selectors.push(selector)
  }
  selectors.sort()
  selectors
}

///|
fn select_text_branch(
  arguments : Map[String, Argument],
  name : String,
  cases : Map[String, Array[MessageNode]],
) -> String raise MessageError {
  let selector = match arguments.get(name) {
    Some(value) => value.as_text()
    None => raise MissingArgument(name)
  }
  if cases.contains(selector) {
    selector
  } else if cases.contains("other") {
    "other"
  } else {
    raise MissingChoice(selector)
  }
}

///|
fn select_numeric_branch(
  arguments : Map[String, Argument],
  name : String,
  kind : String,
  cases : Map[String, Array[MessageNode]],
  locale : String,
) -> (String, Int) raise MessageError {
  let value = required_argument_number(arguments, name)
  let selection = select_plural_branch(
    locale,
    value,
    kind == "selectordinal",
    selectors_for_cases(cases),
  )
  (selection.selected_selector, value)
}

///|
fn evaluate_nodes_with_trace(
  nodes : Array[MessageNode],
  arguments : Map[String, Argument],
  locale : String,
  pound : Int?,
  depth : Int,
  path : String,
  options : FormatOptions,
  events : Array[FormatEvent],
) -> String raise MessageError {
  if depth > options.maximum_depth {
    raise EvaluationLimit(options.maximum_depth)
  }
  let output = StringBuilder::new()
  for index, node in nodes {
    let node_path = child_path(path, index)
    match node {
      TextNode(text) => {
        output.write_string(text)
        events.push(format_event(EmitText, depth, node_path, text))
      }
      ArgumentNode(name) => {
        let value = configurable_argument_text(
          arguments,
          name,
          options.missing_argument,
        )
        output.write_string(value)
        events.push(
          format_event(EmitArgument, depth, node_path, value, argument=name),
        )
      }
      PoundNode => {
        let value = match pound {
          Some(number) => number.to_string()
          None => "#"
        }
        output.write_string(value)
        events.push(format_event(EmitPound, depth, node_path, value))
      }
      ChoiceNode(name, kind, cases) => {
        let (selector, nested_pound) = if kind == "select" {
          (select_text_branch(arguments, name, cases), pound)
        } else {
          let (selected, number) = select_numeric_branch(
            arguments, name, kind, cases, locale,
          )
          (selected, Some(number))
        }
        events.push(
          format_event(
            SelectBranch,
            depth,
            node_path,
            "",
            argument=name,
            selector~,
          ),
        )
        let branch = match cases.get(selector) {
          Some(value) => value
          None => raise MissingChoice(selector)
        }
        output.write_string(
          evaluate_nodes_with_trace(
            branch,
            arguments,
            locale,
            nested_pound,
            depth + 1,
            node_path + ":" + selector,
            options,
            events,
          ),
        )
      }
    }
  }
  output.to_string()
}

///|
/// Format a message and retain a deterministic evaluation trace.
pub fn format_with_trace(
  message : Message,
  arguments : Map[String, Argument],
  locale : String,
  options? : FormatOptions = FormatOptions::default(),
) -> FormattedMessage raise MessageError {
  let events : Array[FormatEvent] = []
  let value = evaluate_nodes_with_trace(
    message.nodes,
    arguments,
    locale,
    None,
    1,
    "",
    options,
    events,
  )
  { value, events }
}

///|
/// Format a message using explicit runtime options.
pub fn format_with_options(
  message : Message,
  arguments : Map[String, Argument],
  locale : String,
  options : FormatOptions,
) -> String raise MessageError {
  format_with_trace(message, arguments, locale, options~).value
}

///|
pub fn FormatEventKind::name(self : FormatEventKind) -> String {
  match self {
    EmitText => "text"
    EmitArgument => "argument"
    EmitPound => "pound"
    SelectBranch => "choice"
  }
}

///|
pub fn FormatEvent::kind(self : FormatEvent) -> FormatEventKind {
  self.kind
}

///|
pub fn FormatEvent::depth(self : FormatEvent) -> Int {
  self.depth
}

///|
pub fn FormatEvent::path(self : FormatEvent) -> String {
  self.path
}

///|
pub fn FormatEvent::argument(self : FormatEvent) -> String? {
  self.argument
}

///|
pub fn FormatEvent::selector(self : FormatEvent) -> String? {
  self.selector
}

///|
pub fn FormatEvent::output(self : FormatEvent) -> String {
  self.output
}

///|
pub fn FormatEvent::is_choice(self : FormatEvent) -> Bool {
  self.kind == SelectBranch
}

///|
pub fn FormatEvent::emits_output(self : FormatEvent) -> Bool {
  self.kind != SelectBranch
}

///|
pub fn FormatEvent::has_argument(self : FormatEvent) -> Bool {
  self.argument is Some(_)
}

///|
pub fn FormatEvent::has_selector(self : FormatEvent) -> Bool {
  self.selector is Some(_)
}

///|
pub fn FormatEvent::display(self : FormatEvent) -> String {
  let detail = match (self.argument, self.selector) {
    (Some(argument), Some(selector)) => " \{argument}=\{selector}"
    (Some(argument), None) => " \{argument}"
    (None, Some(selector)) => " \{selector}"
    (None, None) => ""
  }
  "\{self.path} \{self.kind.name()}\{detail} -> \{self.output}"
}

///|
pub fn FormattedMessage::value(self : FormattedMessage) -> String {
  self.value
}

///|
pub fn FormattedMessage::events(self : FormattedMessage) -> Array[FormatEvent] {
  self.events
}

///|
pub fn FormattedMessage::event_count(self : FormattedMessage) -> Int {
  self.events.length()
}

///|
pub fn FormattedMessage::maximum_depth(self : FormattedMessage) -> Int {
  let mut maximum = 0
  for event in self.events {
    if event.depth > maximum {
      maximum = event.depth
    }
  }
  maximum
}

///|
pub fn FormattedMessage::events_for_argument(
  self : FormattedMessage,
  argument : String,
) -> Array[FormatEvent] {
  let output : Array[FormatEvent] = []
  for event in self.events {
    if event.argument is Some(name) && name == argument {
      output.push(event)
    }
  }
  output
}

///|
pub fn FormattedMessage::choice_events(
  self : FormattedMessage,
) -> Array[FormatEvent] {
  let output : Array[FormatEvent] = []
  for event in self.events {
    if event.kind == SelectBranch {
      output.push(event)
    }
  }
  output
}

///|
pub fn FormattedMessage::trace_text(self : FormattedMessage) -> String {
  let output = StringBuilder::new()
  for index, event in self.events {
    if index > 0 {
      output.write_char('\n')
    }
    output.write_string(event.display())
  }
  output.to_string()
}