///|
/// Serialize a RON value using the parser's canonical compact syntax.
///
/// The result is valid input for `parse` whenever names and identifiers in
/// the value satisfy the RON identifier grammar. Floating-point values that
/// would otherwise look like integers keep a `.0` suffix.
pub fn to_string(value : RonValue) -> String {
  let buffer = StringBuilder()
  write_value(buffer, value)
  buffer.to_string()
}

///|
fn write_value(buffer : StringBuilder, value : RonValue) -> Unit {
  match value {
    Unit => buffer.write_string("()")
    Bool(value) => buffer.write_string(if value { "true" } else { "false" })
    Number(value) => write_number(buffer, value)
    Char(value) => write_char(buffer, value)
    String(value) => write_string(buffer, value)
    Bytes(value) => write_byte_string(buffer, value)
    Byte(value) => write_byte_char(buffer, value)
    Option(value) =>
      match value {
        Some(value) => {
          buffer.write_string("Some(")
          write_value(buffer, value)
          buffer.write_char(')')
        }
        None => buffer.write_string("None")
      }
    Sequence(values) => {
      buffer.write_char('[')
      write_values(buffer, values)
      buffer.write_char(']')
    }
    Tuple(values) => {
      buffer.write_char('(')
      write_values(buffer, values)
      buffer.write_char(')')
    }
    Map(entries) => {
      buffer.write_char('{')
      for i = 0; i < entries.length(); i = i + 1 {
        if i > 0 {
          buffer.write_string(", ")
        }
        let (key, value) = entries[i]
        write_value(buffer, key)
        buffer.write_string(": ")
        write_value(buffer, value)
      }
      buffer.write_char('}')
    }
    Struct(name, fields) => {
      buffer.write_string(name)
      if fields.length() > 0 {
        buffer.write_char('(')
        write_fields(buffer, fields)
        buffer.write_char(')')
      }
    }
    TupleStruct(name, values) => {
      buffer.write_string(name)
      buffer.write_char('(')
      write_values(buffer, values)
      buffer.write_char(')')
    }
    Enum(owner, variant, fields) => {
      buffer.write_string(owner)
      buffer.write_string("::")
      buffer.write_string(variant)
      if fields.length() > 0 {
        buffer.write_char('(')
        write_fields(buffer, fields)
        buffer.write_char(')')
      }
    }
    EnumTuple(owner, variant, values) => {
      buffer.write_string(owner)
      buffer.write_string("::")
      buffer.write_string(variant)
      buffer.write_char('(')
      write_values(buffer, values)
      buffer.write_char(')')
    }
    Range(range) => write_range(buffer, range)
    Identifier(name) => buffer.write_string(name)
  }
}

///|
fn write_range(buffer : StringBuilder, range : RonRange) -> Unit {
  match range.start {
    Some(start) => write_value(buffer, start)
    None => ()
  }
  buffer.write_string(
    if range.inclusive && range.end is Some(_) {
      "..="
    } else {
      ".."
    },
  )
  match range.end {
    Some(end) => write_value(buffer, end)
    None => ()
  }
}

///|
fn write_values(buffer : StringBuilder, values : Array[RonValue]) -> Unit {
  for i = 0; i < values.length(); i = i + 1 {
    if i > 0 {
      buffer.write_string(", ")
    }
    write_value(buffer, values[i])
  }
}

///|
fn write_fields(
  buffer : StringBuilder,
  fields : Array[(String, RonValue)],
) -> Unit {
  for i = 0; i < fields.length(); i = i + 1 {
    if i > 0 {
      buffer.write_string(", ")
    }
    let (name, value) = fields[i]
    buffer.write_string(name)
    buffer.write_string(": ")
    write_value(buffer, value)
  }
}

///|
fn write_number(buffer : StringBuilder, value : RonNumber) -> Unit {
  match value {
    Signed(value) => buffer.write_string(value.to_string())
    Unsigned(value) => buffer.write_string(value.to_string())
    Float(value) => write_float(buffer, value)
    Infinity => buffer.write_string("inf")
    NegativeInfinity => buffer.write_string("-inf")
    NaN => buffer.write_string("NaN")
  }
}

///|
fn write_float(buffer : StringBuilder, value : Double) -> Unit {
  let text = value.to_string()
  buffer.write_string(text)
  if !text.contains(".") && !text.contains("e") && !text.contains("E") {
    buffer.write_string(".0")
  }
}

///|
fn write_string(buffer : StringBuilder, value : String) -> Unit {
  buffer.write_char('"')
  for char in value {
    write_escaped_char(buffer, char, '"')
  }
  buffer.write_char('"')
}

///|
fn write_char(buffer : StringBuilder, value : Char) -> Unit {
  buffer.write_char('\'')
  write_escaped_char(buffer, value, '\'')
  buffer.write_char('\'')
}

///|
fn write_escaped_char(
  buffer : StringBuilder,
  value : Char,
  quote : Char,
) -> Unit {
  match value {
    '\n' => buffer.write_string("\\n")
    '\r' => buffer.write_string("\\r")
    '\t' => buffer.write_string("\\t")
    '\u{0}' => buffer.write_string("\\0")
    '\\' => buffer.write_string("\\\\")
    value if value == quote => {
      buffer.write_char('\\')
      buffer.write_char(value)
    }
    value => {
      let code = value.to_int()
      if code < 0x20 || code == 0x7f {
        buffer.write_string("\\u{")
        buffer.write_string(code.to_string(radix=16))
        buffer.write_char('}')
      } else {
        buffer.write_char(value)
      }
    }
  }
}

///|
fn write_byte_string(buffer : StringBuilder, value : Bytes) -> Unit {
  buffer.write_string("b\"")
  for i = 0; i < value.length(); i = i + 1 {
    write_escaped_byte(buffer, value[i], '"')
  }
  buffer.write_char('"')
}

///|
fn write_byte_char(buffer : StringBuilder, value : Byte) -> Unit {
  buffer.write_string("b'")
  write_escaped_byte(buffer, value, '\'')
  buffer.write_char('\'')
}

///|
fn write_escaped_byte(
  buffer : StringBuilder,
  value : Byte,
  quote : Char,
) -> Unit {
  match value.to_int() {
    value if value == quote.to_int() => {
      buffer.write_char('\\')
      buffer.write_char(quote)
    }
    10 => buffer.write_string("\\n")
    13 => buffer.write_string("\\r")
    9 => buffer.write_string("\\t")
    92 => buffer.write_string("\\\\")
    value if value >= 0x20 && value <= 0x7e =>
      buffer.write_char(value.unsafe_to_char())
    value => {
      buffer.write_string("\\x")
      buffer.write_char(hex_digit(value >> 4))
      buffer.write_char(hex_digit(value & 0xf))
    }
  }
}

///|
fn hex_digit(value : Int32) -> Char {
  if value < 10 {
    ('0'.to_int() + value).unsafe_to_char()
  } else {
    ('a'.to_int() + value - 10).unsafe_to_char()
  }
}