///|
/// Decompose a finite, non-zero `abs` value into its shortest round-trip
/// decimal digits and exponent: `abs == digits * 10^exp`.
///
/// MoonBit's `Double::to_string` already produces the shortest round-trip
/// digits (as ryu does); only the layout differs, so we re-parse it.
fn shortest_digits(abs : Double) -> (String, Int) {
  let chars = abs.to_string().to_array()
  // Split off an optional exponent: "1.5e+300" / "1e-7".
  let mut exp = 0
  let mut mantissa_end = chars.length()
  for i, c in chars {
    if c == 'e' || c == 'E' {
      mantissa_end = i
      let mut negative = false
      let mut e = 0
      for j in (i + 1).. negative = true
          '+' => ()
          d => e = e * 10 + (d.to_int() - '0'.to_int())
        }
      }
      exp = if negative { -e } else { e }
      break
    }
  }
  // Collect the mantissa digits, adjusting the exponent for fraction digits.
  let digits = StringBuilder()
  let mut seen_point = false
  let mut seen_nonzero = false
  for i in 0.. 1 && digits[end - 1] == '0' {
    end -= 1
    exp += 1
  }
  (digits.unsafe_substring(start=0, end~), exp)
}

///|
/// serde_json writes an explicit `+` on positive exponents (`1e+16`).
fn write_exponent(buf : StringBuilder, e : Int) -> Unit {
  buf.write_char('e')
  if e >= 0 {
    buf.write_char('+')
  }
  buf.write_string(e.to_string())
}

///|
/// Format a finite `f64` exactly like serde_json 1.0.151's float writer
/// (ryu's shortest digits and layout, with `e+` for positive exponents).
pub fn format_f64(d : Double) -> String {
  let buf = StringBuilder()
  if d < 0.0 || (d == 0.0 && 1.0 / d < 0.0) {
    buf.write_char('-')
  }
  let abs = d.abs()
  if abs == 0.0 {
    buf.write_string("0.0")
    return buf.to_string()
  }
  let (digits, k) = shortest_digits(abs)
  write_layout(buf, digits, k, min_kk=-4, max_kk=16)
  buf.to_string()
}

///|
/// Lay out `digits * 10^k` like zmij (serde_json's float writer). With
/// `kk` the number of integer digits (`10^(kk-1) <= value < 10^kk`), plain
/// decimal notation is used when `min_kk <= kk <= max_kk`, i.e. zmij's
/// `FIXED_DEC_EXP` shifted by one (f64: -5..=15, f32: -6..=12).
fn write_layout(
  buf : StringBuilder,
  digits : String,
  k : Int,
  min_kk~ : Int,
  max_kk~ : Int,
) -> Unit {
  let length = digits.length()
  let kk = length + k // 10^(kk-1) <= abs < 10^kk
  if 0 <= k && kk <= max_kk {
    // 1234e7 -> 12340000000.0
    buf.write_string(digits)
    for _ in 0.. 12.34
    buf.write_string(digits.unsafe_substring(start=0, end=kk))
    buf.write_char('.')
    buf.write_string(digits.unsafe_substring(start=kk, end=length))
  } else if min_kk <= kk && kk <= 0 {
    // 1234e-6 -> 0.001234
    buf.write_string("0.")
    for _ in 0..<-kk {
      buf.write_char('0')
    }
    buf.write_string(digits)
  } else if length == 1 {
    // 1e30
    buf.write_string(digits)
    write_exponent(buf, kk - 1)
  } else {
    // 1234e30 -> 1.234e33
    buf.write_string(digits.unsafe_substring(start=0, end=1))
    buf.write_char('.')
    buf.write_string(digits.unsafe_substring(start=1, end=length))
    write_exponent(buf, kk - 1)
  }
}