///|
fn Float::ceil_pixels(self : Float) -> Int {
  (self / 1000.0).ceil().to_int()
}

///|
fn FontMetrics::profile_scale(self : FontMetrics) -> Int {
  match self.profile {
    Sans => 1000
    Serif => 1020
    Monospace => 1000
  }
}

///|
fn FontMetrics::scaled_advance(
  self : FontMetrics,
  base_width : Float,
  char : Char,
) -> Float {
  let base_width = match self.profile {
    Monospace => if char.is_zero_width() { 0.0F } else { 8.0F }
    _ => base_width
  }
  let weight_progress = (self.font_weight - 400).clamp(min=0, max=500)
  let weight_scale = match self.profile {
    Monospace => 100
    _ => 100 + (self.bold_width_scale - 100) * weight_progress / 300
  }
  let style_scale = match (self.profile, self.style) {
    (Monospace, _) | (_, Normal) => 100
    (_, Italic) => self.italic_width_scale
  }
  let advance = base_width * Float::from_int(self.font_size) * 1000.0F / 13.0F
  let advance = advance * Float::from_int(self.width_scale) / 100.0F
  let advance = advance * Float::from_int(self.glyph_width_scale(char)) / 100.0F
  let advance = advance * Float::from_int(self.font_stretch) / 100.0F
  let advance = advance * Float::from_int(self.profile_scale()) / 1000.0F
  let advance = advance * Float::from_int(weight_scale) / 100.0F
  advance * Float::from_int(style_scale) / 100.0
}

///|
/// Return the deterministic PlantUML table advance in fixed-point units.
/// The Java width table is generated at size 16 and stores tenths of a point.
fn FontMetrics::table_advance(self : FontMetrics, char : Char) -> Float? {
  guard self.profile == Sans else { None }
  let code_point = char.to_int()
  guard code_point < 0xFFFF else { Some(160.0F) }
  let block = code_point >> 8
  guard block < plantuml_sans_serif_widths.length() else { Some(130.0F) }
  let widths = plantuml_sans_serif_widths[block]
  let value = unicode_block_width(widths, code_point & 0xFF)
  Some(
    Float::from_int(value) * 100.0F * Float::from_int(self.font_size) / 16.0F,
  )
}

///|
/// Match PlantUML's UnicodeBlock representation: one-value blocks are
/// constant, full blocks are direct lookup tables, and shorter blocks store
/// `(count, value)` RLE pairs.
fn unicode_block_width(widths : Array[Int], low_byte : Int) -> Int {
  if widths.length() == 1 {
    widths[0]
  } else if widths.length() == 256 {
    widths[low_byte]
  } else {
    for cursor = 0, decoded_index = 0 {
      guard cursor < widths.length() else { break 0 }
      let count = widths[cursor]
      let next_value = widths[cursor + 1]
      guard low_byte < decoded_index + count else {
        continue cursor + 2, decoded_index + count
      }
      break next_value
    }
  }
}

///|
fn FontMetrics::glyph_width_scale(self : FontMetrics, char : Char) -> Int {
  guard self.profile != Monospace else { 100 }
  match char {
    'A'..='Z' => self.uppercase_width_scale
    'a'..='z' => self.lowercase_width_scale
    '0'..='9' => self.digit_width_scale
    ' ' | '\t' => 100
    _ if char.is_ascii() => self.punctuation_width_scale
    _ if char.is_emoji() => self.emoji_width_scale
    _ if char.is_wide() => self.wide_width_scale
    _ => 100
  }
}

///|
pub const MONO_DIGIT_BASE_WIDTH : Float = 8.5

///|
fn FontMetrics::digit_base_width(self : FontMetrics, char : Char) -> Float {
  match (self.profile, char) {
    (Sans, '1') => MONO_DIGIT_BASE_WIDTH * 0.6
    (Serif, '1') => MONO_DIGIT_BASE_WIDTH * 0.8
    _ => MONO_DIGIT_BASE_WIDTH
  }
}

///|
fn FontMetrics::base_char_width(self : FontMetrics, char : Char) -> Float {
  guard !char.is_zero_width() else { 0.0 }
  match char {
    ' ' | '\t' => 4.0
    'i' | 'l' | 'I' | '!' | '|' | '.' | ',' | ':' | ';' | '\'' => 3.0
    'f' | 'j' | 'r' | 't' | '(' | ')' | '[' | ']' => 5.0
    'm' | 'w' | 'M' | 'W' | '@' | '%' => 11.0
    'A'..='Z' => 8.0
    '0'..='9' => self.digit_base_width(char)
    _ if char.is_emoji() || char.is_wide() => 14.0
    _ => 7.0
  }
}

///|
fn FontMetrics::char_advance(self : FontMetrics, char : Char) -> Float {
  self
  .table_advance(char)
  .map_or_else(() => self.scaled_advance(self.base_char_width(char), char), advance => {
    let advance = advance * Float::from_int(self.width_scale) / 100.0F
    let advance = advance *
      Float::from_int(self.glyph_width_scale(char)) /
      100.0F
    let advance = advance * Float::from_int(self.font_stretch) / 100.0F
    let weight_progress = (self.font_weight - 400).clamp(min=0, max=500)
    let weight_scale = 100 +
      (self.bold_width_scale - 100) * weight_progress / 300
    let advance = advance * Float::from_int(weight_scale) / 100.0F
    match self.style {
      Normal => advance
      Italic => advance * Float::from_int(self.italic_width_scale) / 100.0F
    }
  })
}

///|
pub fn FontMetrics::char_width(self : FontMetrics, char : Char) -> Int {
  self.char_advance(char).ceil_pixels()
}

///|
fn FontMetrics::kerning(self : FontMetrics, left : Char, right : Char) -> Float {
  guard self.profile != Monospace else { 0.0 }
  let base = match (left, right) {
    ('A', 'V' | 'W' | 'Y') | ('T', 'a' | 'e' | 'o') | ('Y', 'a' | 'o') => -1000
    ('F' | 'P', 'a' | 'e' | 'o') => -500
    _ => 0
  }
  Float::from_int(base * self.font_size * self.kerning_scale) / 1300.0F
}