///|
pub struct ObjectSchema {
  properties : Map[String, JsonSchema]?
  required : Array[String]?
  additional_properties : JsonSchema?
} derive (
  Debug,
  Eq,
  FromJson(
    fields(
      additional_properties(rename="additionalProperties"),
      pattern_properties(rename="patternProperties"),
      property_names(rename="propertyNames"),
    ),
  ),
)

///|
impl Validatable for ObjectSchema with fn validate(
  self,
  value,
  resolver~,
  json_path~,
  schema_path~,
) {
  guard value is Object(obj) else {
    return [
      ValidationError::new(
        value,
        json_path,
        schema_path,
        "\{@debug.to_string(value)} is not an object",
      ),
    ]
  }
  // Required properties
  let errors : Array[ValidationError] = []

  // let required_keys: Array[String] = []
  if self.required is Some(required_keys) {
    let non_exsited_keys = required_keys.filter(fn(key) { !obj.contains(key) })
    for key in non_exsited_keys {
      errors.push(
        ValidationError::new(
          value,
          json_path.key(key),
          schema_path.key("required"),
          "Required property '\{key}' is missing",
        ),
      )
    }
  }
  // TODO: {type: string}
  let required_keys = match self.required {
    Some(keys) => Set::from_array(keys)
    None => Set([])
  }
  let props_keys = match self.properties {
    Some(props) => props.keys() |> Set::from_iter
    None => Set([])
  }
  let additional_props = obj
    .keys()
    .filter(fn(key) {
      !required_keys.contains(key) && !props_keys.contains(key)
    })
    |> Set::from_iter
  if self.additional_properties is Some(schema) {
    for key in additional_props {
      guard obj.get(key) is Some(child_value) else { continue }
      let child_errors = schema.validate(
        child_value,
        json_path=json_path.key(key),
        schema_path=schema_path.key("additionalProperties"),
        resolver~,
      )
      errors.append(child_errors)
    }
  } else if additional_props.length() > 0 {
    for key in additional_props {
      errors.push(
        ValidationError::new(
          value,
          json_path.key(key),
          schema_path.key("additionalProperties"),
          "Additional property '\{@debug.to_string(additional_props)}' is not allowed",
        ),
      )
    }
  }
  // Properties validation
  if self.properties is Some(props) {
    props.each(fn(prop_name, child_schema) {
      guard obj.get(prop_name) is Some(child_value) else { return }
      let child_errors = child_schema.validate(
        child_value,
        json_path=json_path.key(prop_name),
        schema_path=schema_path.key("properties").key(prop_name),
        resolver~,
      )
      errors.append(child_errors)
    })
    // No properties to validate
  }
  errors
}