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

///|
/// Wraps an upstream YAML value for standard JSON conversion.
pub fn Yaml::Yaml(value : @upstream_yaml.Yaml) -> Yaml {
  { value, }
}

///|
/// Converts wrapped YAML into flat JSON.
///
/// YAML `inf` and `nan` reals abort conversion because JSON cannot represent
/// non-finite numbers. `Yaml::BadValue` also has no JSON representation.
pub impl ToJson for Yaml with fn to_json(self) {
  match self.value {
    @upstream_yaml.Null => Json::null()
    @upstream_yaml.Map(values) =>
      Json::object(values.map((_, value) => Yaml::Yaml(value).to_json()))
    @upstream_yaml.Array(values) =>
      Json::array(values.map(value => Yaml::Yaml(value).to_json()))
    @upstream_yaml.Boolean(value) => Json::boolean(value)
    @upstream_yaml.String(value) => Json::string(value)
    @upstream_yaml.Integer(value) =>
      Json::number(value.to_double(), repr=value.to_string())
    @upstream_yaml.Real(value, repr~) => {
      if value.is_nan() ||
        value == @double.infinity ||
        value == @double.neg_infinity {
        abort("non-finite YAML reals cannot be represented as JSON")
      }
      Json::number(value, repr~)
    }
    @upstream_yaml.BadValue =>
      abort("Yaml::BadValue cannot be represented as JSON")
  }
}

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

///|
/// Converts JSON values into the corresponding flat wrapped YAML value.
///
/// JSON numbers with an integer representation become `Yaml::Integer`; all
/// other finite JSON numbers become `Yaml::Real`. Non-finite numbers are
/// rejected, and `Yaml::BadValue` is never produced.
pub impl @json.FromJson for Yaml with fn from_json(json, path) {
  match json {
    Json::Null => Yaml::Yaml(@upstream_yaml.Yaml::Null)
    Json::True => Yaml::Yaml(@upstream_yaml.Yaml::Boolean(true))
    Json::False => Yaml::Yaml(@upstream_yaml.Yaml::Boolean(false))
    Json::String(value) => Yaml::Yaml(@upstream_yaml.Yaml::String(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, "YAML numbers must be finite"))
      }
      let representation = match repr {
        Some(representation) => representation
        None => value.to_string()
      }
      try @string.parse_int64(representation) catch {
        _ => Yaml::Yaml(@upstream_yaml.Yaml::Real(value, repr=representation))
      } noraise {
        integer => Yaml::Yaml(@upstream_yaml.Yaml::Integer(integer))
      }
    }
    Json::Array(_) => {
      let values : Array[Yaml] = @json.from_json(json, path~)
      Yaml::Yaml(@upstream_yaml.Yaml::Array(values.map(value => value.value)))
    }
    Json::Object(_) => {
      let values : Map[String, Yaml] = @json.from_json(json, path~)
      Yaml::Yaml(
        @upstream_yaml.Yaml::Map(values.map((_, value) => value.value)),
      )
    }
  }
}