///|
/// A runtime value supplied to a localized message.
pub(all) enum MessageValue {
  StringValue(String)
  IntValue(Int)
  DecimalValue(Decimal)
  BoolValue(Bool)
} derive(Eq, Debug)

///|
pub fn MessageValue::from_string(value : String) -> MessageValue {
  StringValue(value)
}

///|
pub fn MessageValue::from_int(value : Int) -> MessageValue {
  IntValue(value)
}

///|
pub fn MessageValue::from_decimal(value : Decimal) -> MessageValue {
  DecimalValue(value)
}

///|
pub fn MessageValue::from_bool(value : Bool) -> MessageValue {
  BoolValue(value)
}

///|
/// A named value passed to `MessageTemplate::format`.
pub struct MessageArgument {
  name : String
  value : MessageValue
} derive(Eq, Debug)

///|
pub fn MessageArgument::new(
  name : String,
  value : MessageValue,
) -> MessageArgument {
  { name, value }
}

///|
pub fn string_argument(name : String, value : String) -> MessageArgument {
  { name, value: StringValue(value) }
}

///|
pub fn int_argument(name : String, value : Int) -> MessageArgument {
  { name, value: IntValue(value) }
}

///|
pub fn decimal_argument(
  name : String,
  coefficient : Int,
  scale : Int,
) -> MessageArgument {
  { name, value: DecimalValue(Decimal::new(coefficient, scale)) }
}

///|
pub fn bool_argument(name : String, value : Bool) -> MessageArgument {
  { name, value: BoolValue(value) }
}

///|
pub fn MessageArgument::name(self : MessageArgument) -> String {
  self.name
}

///|
pub fn MessageArgument::value(self : MessageArgument) -> MessageValue {
  self.value
}

///|
/// Errors produced while rendering an otherwise valid message template.
pub(all) enum MessageFormatError {
  MissingArgument(String)
  DuplicateArgument(String)
  ExpectedNumber(String)
  ExpectedSelector(String)
  NoMatchingCase(String)
} derive(Eq, Debug)

///|
pub fn MessageFormatError::message(self : MessageFormatError) -> String {
  match self {
    MissingArgument(name) => "missing message argument: \{name}"
    DuplicateArgument(name) => "duplicate message argument: \{name}"
    ExpectedNumber(name) => "message argument '\{name}' must be an integer"
    ExpectedSelector(name) =>
      "message argument '\{name}' cannot be used as a select key"
    NoMatchingCase(name) =>
      "message argument '\{name}' has no matching or other case"
  }
}

///|
priv struct MessageRenderContext {
  locale : Locale
  arguments : Array[MessageArgument]
  number_formatter : NumberFormatter
}

///|
fn validate_message_arguments(
  arguments : Array[MessageArgument],
) -> Result[Unit, MessageFormatError] {
  for index, argument in arguments {
    for previous = 0; previous < index; previous = previous + 1 {
      if arguments[previous].name == argument.name {
        return Err(DuplicateArgument(argument.name))
      }
    }
  }
  Ok(())
}

///|
fn find_message_value(
  arguments : Array[MessageArgument],
  name : String,
) -> MessageValue? {
  for argument in arguments {
    if argument.name == name {
      return Some(argument.value)
    }
  }
  None
}

///|
fn MessageRenderContext::require(
  self : MessageRenderContext,
  name : String,
) -> Result[MessageValue, MessageFormatError] {
  match find_message_value(self.arguments, name) {
    Some(value) => Ok(value)
    None => Err(MissingArgument(name))
  }
}

///|
fn render_plain_value(
  context : MessageRenderContext,
  value : MessageValue,
) -> String {
  match value {
    StringValue(text) => text
    IntValue(number) => context.number_formatter.format_int(number)
    DecimalValue(decimal) =>
      DecimalFormatter::new(context.locale).format(decimal)
    BoolValue(true) => "true"
    BoolValue(false) => "false"
  }
}

///|
fn select_key(
  name : String,
  value : MessageValue,
) -> Result[String, MessageFormatError] {
  match value {
    StringValue(text) => Ok(text)
    IntValue(number) => Ok(number.to_string())
    BoolValue(true) => Ok("true")
    BoolValue(false) => Ok("false")
    DecimalValue(_) => Err(ExpectedSelector(name))
  }
}

///|
fn find_message_branch(
  cases : Array[MessageCase],
  selector : String,
) -> MessageCase? {
  for branch in cases {
    if branch.selector == selector {
      return Some(branch)
    }
  }
  None
}

///|
fn select_message_branch(
  cases : Array[MessageCase],
  selector : String,
) -> MessageCase? {
  match find_message_branch(cases, selector) {
    Some(branch) => Some(branch)
    None => find_message_branch(cases, "other")
  }
}

///|
fn replace_plural_marker(text : String, replacement : String) -> String {
  text.replace_all(old="#", new=replacement)
}

///|
fn render_message_nodes(
  context : MessageRenderContext,
  nodes : Array[MessageNode],
  plural_number : Int?,
) -> Result[String, MessageFormatError] {
  let output = StringBuilder::new()
  for node in nodes {
    match node {
      Text(text) =>
        match plural_number {
          Some(number) =>
            output.write_string(
              replace_plural_marker(
                text,
                context.number_formatter.format_int(number),
              ),
            )
          None => output.write_string(text)
        }
      Argument(name) =>
        match context.require(name) {
          Ok(value) => output.write_string(render_plain_value(context, value))
          Err(error) => return Err(error)
        }
      Select(name, cases) => {
        let value = match context.require(name) {
          Ok(found) => found
          Err(error) => return Err(error)
        }
        let key = match select_key(name, value) {
          Ok(found) => found
          Err(error) => return Err(error)
        }
        let branch = match select_message_branch(cases, key) {
          Some(found) => found
          None => return Err(NoMatchingCase(name))
        }
        match render_message_nodes(context, branch.nodes, plural_number) {
          Ok(rendered) => output.write_string(rendered)
          Err(error) => return Err(error)
        }
      }
      Plural(name, offset, kind, cases) => {
        let value = match context.require(name) {
          Ok(IntValue(number)) => number
          Ok(_) => return Err(ExpectedNumber(name))
          Err(error) => return Err(error)
        }
        let exact_selector = "=\{value}"
        let branch = match find_message_branch(cases, exact_selector) {
          Some(found) => Some(found)
          None => {
            let category = PluralRules::new(context.locale, kind~)
              .select(value - offset)
              .name()
            select_message_branch(cases, category)
          }
        }
        match branch {
          None => return Err(NoMatchingCase(name))
          Some(found) =>
            match
              render_message_nodes(context, found.nodes, Some(value - offset)) {
              Ok(rendered) => output.write_string(rendered)
              Err(error) => return Err(error)
            }
        }
      }
    }
  }
  Ok(output.to_string())
}

///|
/// Renders this template using locale-aware plural rules and numbers.
pub fn MessageTemplate::format(
  self : MessageTemplate,
  locale : Locale,
  arguments : Array[MessageArgument],
) -> Result[String, MessageFormatError] {
  match validate_message_arguments(arguments) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let context : MessageRenderContext = {
    locale,
    arguments,
    number_formatter: NumberFormatter::new(locale),
  }
  render_message_nodes(context, self.nodes, None)
}

///|
/// Parses and formats a one-off template.
///
/// Applications that reuse a message should parse it once and retain the
/// `MessageTemplate` instead.
pub fn format_message(
  locale : Locale,
  source : String,
  arguments : Array[MessageArgument],
) -> Result[String, String] {
  match MessageTemplate::parse(source) {
    Err(error) => Err(error.message())
    Ok(template) =>
      match template.format(locale, arguments) {
        Ok(value) => Ok(value)
        Err(error) => Err(error.message())
      }
  }
}