///|
fn get_start_at(node : @dom.Node) -> Int {
  let raw = @domext.attr_or(node, "start", "1")
  @string.parse_int(raw) catch {
    _ => 1
  }
}

///|
/// Pad an ordered-list index to `width`, keeping a leading minus sign before
/// any zero padding.
fn pad_ordered_list_index(index : Int, width : Int) -> String {
  let raw = index.to_string()
  match raw.strip_prefix("-") {
    Some(digits) => {
      let zero_count = (width - 1 - digits.length()).max(0)
      "-" + "0".repeat(zero_count) + digits.to_owned()
    }
    None => "0".repeat((width - raw.length()).max(0)) + raw
  }
}

///|
/// Build the prefix function for a list (port of `commonmark.getPrefixFunc`):
/// bullet marker for `ul`, zero-padded "N. " for `ol`.
fn make_prefix(
  ctx : RenderCtx,
  node : @dom.Node,
  slice_length : Int,
) -> (Int) -> String {
  let start_at = get_start_at(node)
  let is_ul = @domext.node_name(node) is "ul"
  let last_index = start_at + slice_length - 1
  let max_length = start_at
    .to_string()
    .length()
    .max(last_index.to_string().length())
  fn prefix(slice_index : Int) -> String {
    if is_ul {
      ctx.options.bullet_list_marker + " "
    } else {
      let current_index = start_at + slice_index
      // Pad numbers so every prefix lines up, e.g. "01. ".
      let padded = pad_ordered_list_index(current_index, max_length)
      padded + ". "
    }
  }

  prefix
}

///|
/// Render a multi-line list item with the right indentation, including the
/// indent applied after code-block newline markers (port of
/// `commonmark.renderMultiLineListItem`).
fn render_multi_line_list_item(
  out : StringBuilder,
  content : String,
  indent_count : Int,
) -> Unit {
  let indent = " ".repeat(indent_count)
  let indented_code_newline = @escape.marker_code_block_newline + indent
  let lines = content.split("\n").collect()
  for i, line in lines {
    let line = line
      .to_owned()
      .replace_all(
        old=@escape.marker_code_block_newline,
        new=indented_code_newline,
      )
    if i != 0 {
      // The first line is already indented through the prefix.
      out.write_string(indent)
    }
    out.write_string(line)
    if i < lines.length() - 1 {
      out.write_char('\n')
    }
  }
}

///|
/// Render a `ul`/`ol` list container (port of
/// `commonmark.renderListContainer`).
fn render_list_container(
  ctx : RenderCtx,
  out : StringBuilder,
  node : @dom.Node,
) -> Unit {
  let items : Array[String] = []
  for child in node.children.copy() {
    let buf = StringBuilder()
    render_node(ctx, buf, child)
    let content = @textutils.trim_space(buf.to_string())
    if content != "" {
      items.push(content)
    }
  }
  if items.length() == 0 {
    return
  }
  let prefix = make_prefix(ctx, node, items.length())
  let indent_count = prefix(0).length()
  out.write_string("\n\n")
  for i, item in items {
    out.write_string(prefix(i))
    let item = @textutils.trim_consecutive_newlines(item)
    let item = @textutils.trim_unnecessary_hard_line_breaks(item)
    let item = unescape(ctx, item)
    render_multi_line_list_item(out, item, indent_count)
    if i < items.length() - 1 {
      out.write_char('\n')
    }
  }
  out.write_string("\n\n")
}