// Typographic punctuation, the pass `Flavor::smart` turns on. It runs while the
// text is still the source, which is what makes it agree with the reference:
// an escaped quote is not a quote, an entity is not a quote, and the characters
// on either side of a run decide which way a quote curls.

///|
/// Whether a character at `i` could open and could close, by the flanking rules
/// the specification gives delimiters.
fn flanking_of(input : StringView, i : Int32) -> (Bool, Bool) {
  let before = char_before(input, i)
  let after = if i + 1 >= input.length() { '\n' } else { char_at(input, i + 1) }
  let space_before = is_unicode_space(before)
  let space_after = is_unicode_space(after)
  let punct_before = is_unicode_punct(before)
  let punct_after = is_unicode_punct(after)
  let left = !space_after && (!punct_after || space_before || punct_before)
  let right = !space_before && (!punct_before || space_after || punct_after)
  (left, right)
}

///|
/// Whether a single quote that could close sits anywhere later in the block.
/// A quote with none is the apostrophe of `'tis`, which stays a closing quote.
fn has_closing_quote(input : StringView, from : Int32) -> Bool {
  let n = input.length()
  let mut i = from
  while i < n {
    let c = input.get_char(i).unwrap()
    if c == '\'' {
      let (_, right) = flanking_of(input, i)
      if right {
        return true
      }
    }
    i = i + c.utf16_len()
  }
  false
}

///|
/// How a run of hyphens is written: the reference reads three as an em dash,
/// two as an en dash, and splits a longer run the way a proof would.
fn dash_counts(run : Int32) -> (Int32, Int32) {
  if run % 3 == 0 {
    (run / 3, 0)
  } else if run % 2 == 0 {
    (0, run / 2)
  } else if run % 3 == 2 {
    ((run - 2) / 3, 1)
  } else {
    ((run - 4) / 3, 2)
  }
}

///|
/// How many times `ch` occurs from `start` on, up to `end`.
fn run_length(
  input : StringView,
  start : Int32,
  end : Int32,
  ch : Char,
) -> Int32 {
  let mut i = start
  let mut count = 0
  while i < end && input.get_char(i) == Some(ch) {
    count = count + 1
    i = i + ch.utf16_len()
  }
  count
}

///|
/// Rewrite the characters of one run of plain text. `input` is the whole block,
/// so the characters on either side of the run still decide a quote's direction,
/// and a closing quote further on is still seen.
fn smarten_run(input : StringView, start : Int32, end : Int32) -> String {
  let out = StringBuilder()
  let mut i = start
  while i < end {
    let c = input.get_char(i).unwrap()
    let step = c.utf16_len()
    if c == '"' {
      let (_, right) = flanking_of(input, i)
      out.write_char(if right { '\u{201D}' } else { '\u{201C}' })
    } else if c == '\'' {
      let (left, _) = flanking_of(input, i)
      let opens = left && has_closing_quote(input, i + step)
      out.write_char(if opens { '\u{2018}' } else { '\u{2019}' })
    } else if c == '-' {
      let run = run_length(input, i, end, '-')
      if run == 1 {
        out.write_char('-')
      } else {
        let (em, en) = dash_counts(run)
        for _ in 0..= 3 {
        out.write_char('\u{2026}')
        left = left - 3
      }
      for _ in 0..