///|
// Decimal physical lengths are retained as validated tokens, not rounded pixels
// or approximate floating-point millimeters. 12 digits bound Int64 arithmetic.
fn physical_length(text : String) -> (String, Int64, Int64)? {
  let text = text.trim().to_owned()
  let (unit, mul, div) = if text.has_suffix("mm") {
    ("mm", 1L, 1L)
  } else if text.has_suffix("cm") {
    ("cm", 10L, 1L)
  } else if text.has_suffix("in") {
    ("in", 127L, 5L)
  } else if text.has_suffix("pt") {
    ("pt", 127L, 360L)
  } else if text.has_suffix("pc") {
    ("pc", 127L, 30L)
  } else {
    return None
  }
  let raw = text[:text.length() - unit.length()].to_owned()
  let mut num = 0L
  let mut scale = 1L
  let mut fraction = false
  let mut digits = 0
  for c in raw {
    if c == '.' && !fraction {
      fraction = true
      continue
    }
    if c < '0' || c > '9' || digits >= 12 {
      return None
    }
    num = num * 10L + (c.to_int() - 48).to_int64()
    if fraction {
      scale *= 10L
    }
    digits += 1
  }
  if digits == 0 {
    return None
  }
  let numerator = num * mul
  let denominator = scale * div
  if numerator <= 0L || numerator > 500L * denominator {
    return None
  }
  Some((text, numerator, denominator))
}

///|
/// Set an exact physical column length in mm/cm/in/pt/pc (0 < mm <= 500).
/// At most 12 decimal digits; no sign, exponent, percent or pixel lengths.
/// The validated token is retained; no DPI, rounding or visual-fit promise.
pub fn Sheet::set_column_width_length(
  self : Sheet,
  col : Int32,
  length : String,
) -> Unit raise OdsError {
  position(0, col)
  let (length, _, _) = match physical_length(length) {
    Some(v) => v
    None =>
      raise Invalid(
        "column length must be a bounded positive physical decimal (<=500mm)",
      )
  }
  let cols = if col + 1 > self.cols { col + 1 } else { self.cols }
  if self.rows * cols > 65536 {
    raise Invalid("sheet rendered grid exceeds 65536 cells")
  }
  self.precise_widths.set(col, length)
  match read_width_mm(length) {
    Some(mm) => self.widths.set(col, mm)
    None => self.widths.remove(col)
  }
  self.cols = cols
}

///|
/// Exact stored token, or integer-mm token for the legacy width API.
pub fn Sheet::column_width_length(self : Sheet, col : Int32) -> String? {
  match self.precise_widths.get(col) {
    Some(v) => Some(v)
    None =>
      match self.widths.get(col) {
        Some(mm) => Some("\{mm}mm")
        None => None
      }
  }
}

///|
/// Set a fixed physical row height. Automatic/content-fit heights are not modeled.
pub fn Sheet::set_row_height_length(
  self : Sheet,
  row : Int32,
  length : String,
) -> Unit raise OdsError {
  position(row, 0)
  let (length, _, _) = match physical_length(length) {
    Some(v) => v
    None =>
      raise Invalid(
        "row height must be a bounded positive physical decimal (<=500mm)",
      )
  }
  let rows = if row + 1 > self.rows { row + 1 } else { self.rows }
  if rows * self.cols > 65536 {
    raise Invalid("sheet rendered grid exceeds 65536 cells")
  }
  self.row_heights.set(row, length)
  self.rows = rows
}

///|
pub fn Sheet::row_height_length(self : Sheet, row : Int32) -> String? {
  self.row_heights.get(row)
}

///|
fn read_width_mm(text : String) -> Int32? {
  match physical_length(text) {
    Some((_, num, den)) =>
      if num % den == 0L && num / den >= 1L {
        Some((num / den).to_int())
      } else {
        None
      }
    None => None
  }
}