///|
priv struct ChoiceCollector {
  choices : Array[ChoiceUse]
  mut ordinal : Int
}

///|
fn ChoiceCollector::new() -> ChoiceCollector {
  { choices: [], ordinal: 0 }
}

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

///|
fn collect_choice_uses(
  nodes : Array[MessageNode],
  depth : Int,
  collector : ChoiceCollector,
) -> Unit {
  for node in nodes {
    match node {
      ChoiceNode(argument, kind_name, cases) => {
        let kind = match ChoiceKind::from_name(kind_name) {
          Some(value) => value
          None => Select
        }
        let ordinal = collector.ordinal
        collector.ordinal += 1
        collector.choices.push({
          argument,
          kind,
          selectors: sorted_selectors(cases),
          depth,
          ordinal,
        })
        for _, branch in cases {
          collect_choice_uses(branch, depth + 1, collector)
        }
      }
      TextNode(_) | ArgumentNode(_) | PoundNode => ()
    }
  }
}

///|
/// Return a semantic signature for linting and tooling.
pub fn Message::signature(self : Message) -> MessageSignature {
  let collector = ChoiceCollector::new()
  collect_choice_uses(self.nodes, 1, collector)
  { arguments: self.argument_uses(), choices: collector.choices }
}

///|
pub fn Message::choice_uses(self : Message) -> Array[ChoiceUse] {
  self.signature().choices
}

///|
pub fn Message::nodes(self : Message) -> Array[MessageNode] {
  self.nodes
}

///|
/// Construct a message from AST nodes.
pub fn Message::from_nodes(nodes : Array[MessageNode]) -> Message {
  { nodes, }
}

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

///|
pub fn ChoiceUse::kind(self : ChoiceUse) -> ChoiceKind {
  self.kind
}

///|
pub fn ChoiceUse::selectors(self : ChoiceUse) -> Array[String] {
  self.selectors
}

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

///|
pub fn ChoiceUse::ordinal(self : ChoiceUse) -> Int {
  self.ordinal
}

///|
pub fn ChoiceUse::has_selector(self : ChoiceUse, selector : String) -> Bool {
  for existing in self.selectors {
    if existing == selector {
      return true
    }
  }
  false
}

///|
pub fn ChoiceUse::exact_selectors(self : ChoiceUse) -> Array[String] {
  let output : Array[String] = []
  for selector in self.selectors {
    if selector.has_prefix("=") {
      output.push(selector)
    }
  }
  output
}

///|
pub fn ChoiceUse::category_selectors(self : ChoiceUse) -> Array[String] {
  let output : Array[String] = []
  for selector in self.selectors {
    if !selector.has_prefix("=") {
      output.push(selector)
    }
  }
  output
}

///|
pub fn ChoiceUse::identifier(self : ChoiceUse) -> String {
  "\{self.argument}:\{self.kind.name()}:\{self.ordinal}"
}

///|
pub fn MessageSignature::arguments(
  self : MessageSignature,
) -> Array[ArgumentUse] {
  self.arguments
}

///|
pub fn MessageSignature::choices(self : MessageSignature) -> Array[ChoiceUse] {
  self.choices
}

///|
pub fn MessageSignature::argument(
  self : MessageSignature,
  name : String,
) -> ArgumentUse? {
  for argument in self.arguments {
    if argument.name == name {
      return Some(argument)
    }
  }
  None
}

///|
pub fn MessageSignature::has_argument(
  self : MessageSignature,
  name : String,
) -> Bool {
  self.argument(name) is Some(_)
}

///|
pub fn MessageSignature::choices_for_argument(
  self : MessageSignature,
  name : String,
) -> Array[ChoiceUse] {
  let output : Array[ChoiceUse] = []
  for choice in self.choices {
    if choice.argument == name {
      output.push(choice)
    }
  }
  output
}

///|
pub fn MessageSignature::choices_of_kind(
  self : MessageSignature,
  kind : ChoiceKind,
) -> Array[ChoiceUse] {
  let output : Array[ChoiceUse] = []
  for choice in self.choices {
    if choice.kind == kind {
      output.push(choice)
    }
  }
  output
}

///|
pub fn MessageSignature::choice_count(self : MessageSignature) -> Int {
  self.choices.length()
}

///|
pub fn MessageSignature::argument_count(self : MessageSignature) -> Int {
  self.arguments.length()
}

///|
fn escape_text_node(text : String) -> String {
  let output = StringBuilder::new()
  for ch in text {
    match ch {
      '{' | '}' | '\\' | '#' => {
        output.write_char('\\')
        output.write_char(ch)
      }
      _ => output.write_char(ch)
    }
  }
  output.to_string()
}

///|
fn render_nodes_as_template(nodes : Array[MessageNode]) -> String {
  let output = StringBuilder::new()
  for node in nodes {
    match node {
      TextNode(text) => output.write_string(escape_text_node(text))
      ArgumentNode(name) => {
        output.write_char('{')
        output.write_string(name)
        output.write_char('}')
      }
      PoundNode => output.write_char('#')
      ChoiceNode(name, kind, cases) => {
        output.write_char('{')
        output.write_string(name)
        output.write_string(", ")
        output.write_string(kind)
        output.write_string(", ")
        let selectors = sorted_selectors(cases)
        for index, selector in selectors {
          if index > 0 {
            output.write_char(' ')
          }
          output.write_string(selector)
          output.write_string(" {")
          output.write_string(render_nodes_as_template(cases[selector]))
          output.write_char('}')
        }
        output.write_char('}')
      }
    }
  }
  output.to_string()
}

///|
/// Serialize an AST using MoonL10n's canonical spacing and escaping.
pub fn Message::to_template(self : Message) -> String {
  render_nodes_as_template(self.nodes)
}

///|
fn append_indent(output : StringBuilder, level : Int) -> Unit {
  for _ in 0.. Unit {
  for node in nodes {
    append_indent(output, depth)
    match node {
      TextNode(text) => {
        output.write_string("text ")
        output.write_string(text)
        output.write_char('\n')
      }
      ArgumentNode(name) => {
        output.write_string("argument ")
        output.write_string(name)
        output.write_char('\n')
      }
      PoundNode => output.write_string("pound\n")
      ChoiceNode(name, kind, cases) => {
        output.write_string(kind)
        output.write_char(' ')
        output.write_string(name)
        output.write_char('\n')
        for selector in sorted_selectors(cases) {
          append_indent(output, depth + 1)
          output.write_string("case ")
          output.write_string(selector)
          output.write_char('\n')
          render_ast_nodes(output, cases[selector], depth + 2)
        }
      }
    }
  }
}

///|
/// Render a deterministic, human-readable AST tree.
pub fn Message::ast_text(self : Message) -> String {
  let output = StringBuilder::new()
  render_ast_nodes(output, self.nodes, 0)
  output.to_string()
}

///|
fn role_names(roles : Array[ArgumentRole]) -> String {
  let names : Array[String] = []
  for role in roles {
    names.push(role.name())
  }
  names.join(",")
}

///|
/// Render a compact signature suitable for CLI diagnostics.
pub fn MessageSignature::summary(self : MessageSignature) -> String {
  let output = StringBuilder::new()
  output.write_string("arguments=")
  output.write_string(self.arguments.length().to_string())
  output.write_string(" choices=")
  output.write_string(self.choices.length().to_string())
  for argument in self.arguments {
    output.write_char('\n')
    output.write_string(argument.name)
    output.write_string(": ")
    output.write_string(role_names(argument.roles))
    output.write_string(" x")
    output.write_string(argument.occurrences.to_string())
  }
  output.to_string()
}

///|
fn same_role_set(
  left : Array[ArgumentRole],
  right : Array[ArgumentRole],
) -> Bool {
  if left.length() != right.length() {
    return false
  }
  for role in left {
    let mut found = false
    for candidate in right {
      if role == candidate {
        found = true
        break
      }
    }
    if !found {
      return false
    }
  }
  true
}

///|
/// Compare argument names and roles while ignoring occurrence counts.
pub fn MessageSignature::compatible_with(
  self : MessageSignature,
  other : MessageSignature,
) -> Bool {
  if self.arguments.length() != other.arguments.length() {
    return false
  }
  for argument in self.arguments {
    match other.argument(argument.name) {
      Some(candidate) =>
        if !same_role_set(argument.roles, candidate.roles) {
          return false
        }
      None => return false
    }
  }
  true
}

///|
/// Return sorted argument names used by a message.
pub fn MessageSignature::argument_names(
  self : MessageSignature,
) -> Array[String] {
  let names : Array[String] = []
  for argument in self.arguments {
    names.push(argument.name)
  }
  names.sort()
  names
}