///|
/// Display width of a character in a terminal cell.
///
/// The table below covers the ranges a diagram label realistically uses. It is
/// deliberately a range table rather than a Unicode database: the renderer only
/// needs to keep columns aligned, and every range here is documented so the
/// boundary is explicit instead of guessed.
pub fn char_width(c : Char) -> Int32 {
  let n = c.to_int()
  // Control characters and zero-width marks occupy no cell.
  if n < 0x20 || (n >= 0x7F && n < 0xA0) {
    return 0
  }
  if is_zero_width(n) {
    return 0
  }
  if is_wide(n) {
    return 2
  }
  1
}

///|
fn is_zero_width(n : Int32) -> Bool {
  (n >= 0x0300 && n <= 0x036F) ||
  (n >= 0x200B && n <= 0x200F) ||
  (n >= 0xFE00 && n <= 0xFE0F) ||
  n == 0xFEFF
}

///|
fn is_wide(n : Int32) -> Bool {
  (n >= 0x1100 && n <= 0x115F) ||
  (n >= 0x2E80 && n <= 0x303E) ||
  (n >= 0x3041 && n <= 0x33FF) ||
  (n >= 0x3400 && n <= 0x4DBF) ||
  (n >= 0x4E00 && n <= 0x9FFF) ||
  (n >= 0xA000 && n <= 0xA4CF) ||
  (n >= 0xAC00 && n <= 0xD7A3) ||
  (n >= 0xF900 && n <= 0xFAFF) ||
  (n >= 0xFE30 && n <= 0xFE6F) ||
  (n >= 0xFF00 && n <= 0xFF60) ||
  (n >= 0xFFE0 && n <= 0xFFE6) ||
  (n >= 0x1F300 && n <= 0x1F64F) ||
  (n >= 0x1F900 && n <= 0x1F9FF) ||
  (n >= 0x20000 && n <= 0x3FFFD)
}

///|
/// Display width of a whole string.
pub fn text_width(text : String) -> Int32 {
  let mut total = 0
  for c in text {
    total = total + char_width(c)
  }
  total
}

///|
/// Slice a string so that its display width does not exceed `limit`.
/// Returns the prefix and the width actually used.
pub fn clip_to_width(text : String, limit : Int32) -> (String, Int32) {
  let sb = StringBuilder()
  let mut used = 0
  for c in text {
    let w = char_width(c)
    if used + w > limit {
      break
    }
    sb.write_char(c)
    used = used + w
  }
  (sb.to_string(), used)
}

///|
/// Pad with spaces on the right until the display width reaches `target`.
pub fn pad_right(text : String, target : Int32) -> String {
  let w = text_width(text)
  if w >= target {
    return text
  }
  text + " ".repeat(target - w)
}

///|
/// Pad on both sides as evenly as possible.
pub fn pad_center(text : String, target : Int32) -> String {
  let w = text_width(text)
  if w >= target {
    return text
  }
  let diff = target - w
  let left = diff / 2
  " ".repeat(left) + text + " ".repeat(diff - left)
}