///|
/// Owns an upstream TOML value so standard JSON traits can be implemented
/// without violating MoonBit's foreign-trait implementation rule.
pub struct TomlValue {
  /// The wrapped upstream TOML value, exposed as a public readonly field.
  value : @upstream_toml.TomlValue
} derive(Eq, Debug)

///|
/// Wraps an upstream TOML value for standard JSON conversion.
pub fn TomlValue::TomlValue(value : @upstream_toml.TomlValue) -> TomlValue {
  { value, }
}

///|
/// Converts a wrapped TOML value into its flat JSON representation.
///
/// Date-time variants become their stored date strings, so decoding that JSON
/// produces `TomlString` rather than restoring date-time type information.
/// TOML `inf` and `nan` floats abort conversion because JSON cannot represent
/// non-finite numbers.
pub impl ToJson for TomlValue with fn to_json(self) {
  match self.value {
    @upstream_toml.TomlString(value) => Json::string(value)
    @upstream_toml.TomlInteger(value) =>
      Json::number(value.to_double(), repr=value.to_string())
    @upstream_toml.TomlFloat(value) => {
      if value.is_nan() ||
        value == @double.infinity ||
        value == @double.neg_infinity {
        abort("non-finite TOML floats cannot be represented as JSON")
      }
      Json::number(value)
    }
    @upstream_toml.TomlBoolean(value) => Json::boolean(value)
    @upstream_toml.TomlArray(values) =>
      Json::array(values.map(value => TomlValue::TomlValue(value).to_json()))
    @upstream_toml.TomlTable(values) =>
      Json::object(
        values.map((_, value) => TomlValue::TomlValue(value).to_json()),
      )
    @upstream_toml.TomlDateTime(date_time) =>
      match date_time {
        @upstream_datetime.OffsetDateTime(value)
        | @upstream_datetime.LocalDateTime(value)
        | @upstream_datetime.LocalDate(value)
        | @upstream_datetime.LocalTime(value) => Json::string(value)
      }
  }
}

///|
pub extend TomlValue with ToJson::{to_json}

///|
/// Converts flat JSON values into wrapped TOML values.
///
/// JSON strings always become `TomlString`; date-time type information is not
/// inferred. Non-finite numbers and JSON `null` are rejected because TOML or
/// JSON has no corresponding valid value. Arrays may contain mixed types.
pub impl @json.FromJson for TomlValue with fn from_json(json, path) {
  match json {
    Json::Null => raise @json.JsonDecodeError((path, "TOML has no null value"))
    Json::True => TomlValue::TomlValue(@upstream_toml.TomlBoolean(true))
    Json::False => TomlValue::TomlValue(@upstream_toml.TomlBoolean(false))
    Json::String(value) =>
      TomlValue::TomlValue(@upstream_toml.TomlString(value))
    Json::Number(value, repr~) => {
      // `Json::number(@double.not_a_number)` can construct `Number(NaN)`, so
      // the `Json::Number` variant alone does not guarantee a finite value.
      if value.is_nan() ||
        value == @double.infinity ||
        value == @double.neg_infinity {
        raise @json.JsonDecodeError((path, "TOML numbers must be finite"))
      }
      let representation = match repr {
        Some(representation) => representation
        None => value.to_string()
      }
      try @string.parse_int64(representation) catch {
        _ => TomlValue::TomlValue(@upstream_toml.TomlFloat(value))
      } noraise {
        integer => TomlValue::TomlValue(@upstream_toml.TomlInteger(integer))
      }
    }
    Json::Array(_) => {
      let values : Array[TomlValue] = @json.from_json(json, path~)
      let inner_values = values.map(value => value.value)
      TomlValue::TomlValue(@upstream_toml.TomlArray(inner_values))
    }
    Json::Object(_) => {
      let values : Map[String, TomlValue] = @json.from_json(json, path~)
      TomlValue::TomlValue(
        @upstream_toml.TomlTable(values.map((_, value) => value.value)),
      )
    }
  }
}