///|
/// Structural equality ignoring metadata and extensions (upstream `Roughly`).
fn roughly(a : @schema.Schema, b : @schema.Schema) -> Bool {
  match (a, b) {
    (Bool(x), Bool(y)) => x == y
    (Bool(false), _) | (_, Bool(false)) => false
    (Bool(true), Object(o)) | (Object(o), Bool(true)) => shape(o, any=F_META)
    (Object(a), Object(b)) =>
      a.instance_type == b.instance_type &&
      a.format == b.format &&
      a.enum_values == b.enum_values &&
      a.const_value == b.const_value &&
      roughly_subschemas(a.subschemas, b.subschemas) &&
      a.number == b.number &&
      a.string == b.string &&
      roughly_array(a.array, b.array) &&
      roughly_object(a.object, b.object) &&
      a.reference == b.reference
  }
}

///|
fn roughly_option(a : @schema.Schema?, b : @schema.Schema?) -> Bool {
  match (a, b) {
    (None, None) => true
    (Some(x), Some(y)) => roughly(x, y)
    _ => false
  }
}

///|
fn roughly_schema_array(
  a : Array[@schema.Schema]?,
  b : Array[@schema.Schema]?,
) -> Bool {
  match (a, b) {
    (None, None) => true
    (Some(x), Some(y)) =>
      x.length() == y.length() &&
      x.iter().zip(y.iter()).all(p => roughly(p.0, p.1))
    _ => false
  }
}

///|
fn roughly_subschemas(
  a : @schema.SubschemaValidation?,
  b : @schema.SubschemaValidation?,
) -> Bool {
  match (a, b) {
    (None, None) => true
    (Some(x), Some(y)) =>
      roughly_schema_array(x.all_of, y.all_of) &&
      roughly_schema_array(x.any_of, y.any_of) &&
      roughly_schema_array(x.one_of, y.one_of) &&
      roughly_option(x.not, y.not) &&
      roughly_option(x.if_schema, y.if_schema) &&
      roughly_option(x.then_schema, y.then_schema) &&
      roughly_option(x.else_schema, y.else_schema)
    _ => false
  }
}

///|
fn roughly_array(
  a : @schema.ArrayValidation?,
  b : @schema.ArrayValidation?,
) -> Bool {
  match (a, b) {
    (None, None) => true
    (Some(x), Some(y)) =>
      match (x.items, y.items) {
        (None, None) => true
        (Some(Single(p)), Some(Single(q))) => roughly(p, q)
        (Some(Vec(p)), Some(Vec(q))) => roughly_schema_array(Some(p), Some(q))
        _ => false
      }
    _ => false
  }
}

///|
fn roughly_properties(
  a : @collections.StrMap[@schema.Schema],
  b : @collections.StrMap[@schema.Schema],
) -> Bool {
  let xs = a.to_array()
  let ys = b.to_array()
  xs.length() == ys.length() &&
  xs.iter().zip(ys.iter()).all(p => p.0.0 == p.1.0 && roughly(p.0.1, p.1.1))
}

///|
fn roughly_object(
  a : @schema.ObjectValidation?,
  b : @schema.ObjectValidation?,
) -> Bool {
  match (a, b) {
    (None, None) => true
    (Some(x), Some(y)) =>
      x.max_properties == y.max_properties &&
      x.min_properties == y.min_properties &&
      x.required == y.required &&
      roughly_properties(x.properties, y.properties) &&
      roughly_properties(x.pattern_properties, y.pattern_properties) &&
      roughly_option(x.additional_properties, y.additional_properties) &&
      roughly_option(x.property_names, y.property_names)
    _ => false
  }
}