///|
// The profile implements only CSL's final (year-suffix) disambiguation step.
// Group by rendered citation, not by a metadata shortcut: et-al, initials,
// substitutes and literal organizations may change which items collide.
fn assign_rendered_suffixes(
  items : Array[Item],
  style : Style,
) -> Result[Unit, CslError] {
  let layout = match style.citation {
    Some(x) => x
    None =>
      return Err(
        BadStyle("year-suffix disambiguation requires citation layout"),
      )
  }
  let citation_style = context_style(style, true)
  let groups : Map[String, Array[Item]] = Map([])
  for item in items {
    item.strings.remove("year-suffix") |> ignore
    let rendered = render_nodes(layout.children, item, citation_style, "text")
    let group = groups.get(rendered).unwrap_or([])
    group.push(item)
    groups[rendered] = group
  }
  for _, group in groups {
    if group.length() > 1 {
      // items already have final bibliography order; do not sort by item id.
      for i = 0; i < group.length(); i = i + 1 {
        if !explicit_year_suffix(style) {
          match group[i].date("issued") {
            Some(d) if d.year != 0 => ()
            _ =>
              return Err(
                BadItem("ambiguous citation has no issued year: " + group[i].id),
              )
          }
        }
        group[i].strings["year-suffix"] = year_letter(i)
      }
    }
  }
  // Refuse styles whose declared suffix placement cannot resolve the collision.
  let seen : Map[String, String] = Map([])
  for item in items {
    let value = render_nodes(layout.children, item, citation_style, "text")
    if seen.contains(value) {
      return Err(
        BadStyle("year-suffix did not resolve citation ambiguity: " + value),
      )
    }
    seen[value] = item.id
  }
  Ok(())
}

///|
fn nodes_use_suffix(nodes : Array[Node], style : Style) -> Bool {
  for node in nodes {
    match node {
      Text(_, x) => {
        if x.variable == "year-suffix" {
          return true
        }
        if style.macros.get(x.macro_name) is Some(body) &&
          nodes_use_suffix(body, style) {
          return true
        }
      }
      Group(_, xs) => if nodes_use_suffix(xs, style) { return true }
      Names(_, x) => if nodes_use_suffix(x.substitute, style) { return true }
      Choose(_, branches) =>
        for branch in branches {
          if nodes_use_suffix(branch.children, style) {
            return true
          }
        }
      _ => ()
    }
  }
  false
}

///|
fn explicit_year_suffix(style : Style) -> Bool {
  if style.citation is Some(x) && nodes_use_suffix(x.children, style) {
    return true
  }
  if style.bibliography is Some(x) && nodes_use_suffix(x.children, style) {
    return true
  }
  false
}

///|
// For automatic suffix placement the supported profile has one explicit
// issued/year date per layout. Complex date/conditional placement is rejected.
fn automatic_suffix_dates(nodes : Array[Node], style : Style) -> Int32 {
  let mut count = 0
  for node in nodes {
    match node {
      Date(_, x) => {
        if x.variable != "issued" {
          return -1000
        }
        for part in x.parts {
          if part.name == "year" {
            count = count + 1
          }
        }
      }
      Text(_, x) =>
        if style.macros.get(x.macro_name) is Some(body) {
          count = count + automatic_suffix_dates(body, style)
        }
      Group(_, xs) => count = count + automatic_suffix_dates(xs, style)
      Names(_, x) => if !x.substitute.is_empty() { return -1000 }
      Choose(_, _) => return -1000
      _ => ()
    }
  }
  count
}