///|
/// CSV locations are one-based physical line/column (Unicode code points).
/// JSON schema diagnostics use field paths and have no line/column location.
pub(all) struct Diagnostic {
  code : String
  message : String
  line : Int?
  column : Int?
  field : String?
} derive(Debug, Eq)

///|
pub extend Diagnostic with @debug.Debug::{to_repr}

///|
pub extend Diagnostic with Eq::{equal, not_equal}

///|
fn diagnostic(
  code : String,
  message : String,
  line? : Int,
  column? : Int,
  field? : String,
) -> Diagnostic {
  { code, message, line, column, field, }
}

///|
/// Parse an unsigned plain decimal exactly at 0.1 mm precision, without rounding.
/// The accepted software range is 0.0 through 1000.0 mm.
pub fn parse_diameter_mm(text : String) -> Result[Int, String] {
  if text.is_empty() {
    return Err("Diameter is empty.")
  }
  let mut whole = 0
  let mut fraction = 0
  let mut seen_dot = false
  let mut fraction_digits = 0
  let mut whole_digits = 0
  for character in text {
    if character == '.' && !seen_dot {
      if whole_digits == 0 {
        return Err("Diameter needs a digit before the decimal point.")
      }
      seen_dot = true
    } else {
      let digit = character.to_int() - 48
      if digit < 0 || digit > 9 {
        return Err(
          "Use an unsigned plain decimal without whitespace or exponent.",
        )
      }
      if seen_dot {
        fraction_digits += 1
        if fraction_digits > 1 {
          return Err(
            "At most one decimal place is accepted; values are never rounded.",
          )
        }
        fraction = digit
      } else {
        whole_digits += 1
        if whole_digits > 4 {
          return Err("Diameter exceeds the supported decimal width.")
        }
        whole = whole * 10 + digit
      }
    }
  }
  if seen_dot && fraction_digits == 0 {
    return Err("Decimal point must be followed by one digit.")
  }
  let value = whole * 10 + fraction
  if value > 10000 {
    Err("Diameter exceeds the 1000.0 mm software limit.")
  } else {
    Ok(value)
  }
}

///|
pub fn format_diameter_mm(tenths : Int) -> String {
  let sign = if tenths < 0 { "-" } else { "" }
  let value = if tenths < 0 { -tenths.to_int64() } else { tenths.to_int64() }
  "\{sign}\{value / 10}.\{value % 10}"
}