///|
/// Render strong/em with the configured delimiter on every content line
/// (port of `commonmark.renderBoldItalic`).
fn render_bold_italic(
  ctx : RenderCtx,
  out : StringBuilder,
  node : @dom.Node,
) -> EmphasisEdges {
  let delimiter = match @domext.node_name(node) {
    "strong" | "b" => ctx.options.strong_delimiter
    _ => ctx.options.em_delimiter
  }
  let buf = StringBuilder()
  ignore(render_children(ctx, buf, node))
  out.write_string(
    @textutils.delimiter_for_every_line(buf.to_string(), delimiter),
  )
  { start: Some(delimiter), end: Some(delimiter), }
}

///|
/// Only generated emphasis delimiters count as edges. Literal Markdown text
/// (including EscapeMode::Disabled) must not be mistaken for an HTML style.
priv struct EmphasisEdges {
  start : String?
  end : String?
}

///|
fn EmphasisEdges::none() -> EmphasisEdges {
  { start: None, end: None, }
}

///|
/// Separate distinct emphasis runs without inserting visible whitespace.
/// Inline containers propagate their edges, so transparent spans cannot hide
/// a collision. Whitespace already separates runs and needs no comment.
fn emphasis_needs_separator(
  left : String,
  closing : String?,
  right : String,
  opening : String?,
) -> Bool {
  guard left.rev_iter().next() is Some(before) &&
    right.iter().next() is Some(after) &&
    !before.is_whitespace() &&
    !after.is_whitespace() else {
    return false
  }
  let closing = closing.filter(delimiter => left.has_suffix(delimiter))
  let opening = opening.filter(delimiter => right.has_prefix(delimiter))
  if closing is Some(_) && opening is Some(_) {
    return true
  }
  if opening is Some(delimiter) {
    let first = right[delimiter.length():]
      .iter()
      .filter(ch => ch != @escape.MARKER_ESCAPING)
      .next()
    if delimiter.has_prefix("_") ||
      (
        !before.is_ascii_punctuation() &&
        emphasis_edge_may_be_punctuation(first)
      ) {
      return true
    }
  }
  if closing is Some(delimiter) {
    let last = left[:left.length() - delimiter.length()]
      .rev_iter()
      .filter(ch => ch != @escape.MARKER_ESCAPING)
      .next()
    if delimiter.has_prefix("_") ||
      (!after.is_ascii_punctuation() && emphasis_edge_may_be_punctuation(last)) {
      return true
    }
  }
  false
}

///|
/// ASCII letters and digits are known non-punctuation. Conservatively protect
/// other boundary characters, including Unicode punctuation, without a Unicode
/// category table or a new dependency.
fn emphasis_edge_may_be_punctuation(ch : Char?) -> Bool {
  match ch {
    Some(ch) => !(ch.is_ascii_alphabetic() || ch.is_ascii_digit())
    None => false
  }
}