///|
/// Create a schema that validates JSON numbers.
pub fn number(
  required_error? : String = "",
  invalid_type_error? : String = "",
) -> Schema {
  {
    schema_type: NumberType,
    rules: [],
    description: "",
    required_error,
    invalid_type_error,
    name: "",
    brand: "",
  }
}

///|
/// Require the number to be an integer (no fractional part).
pub fn Schema::int(self : Schema, msg? : String = "") -> Schema {
  match inner_type(self.schema_type) {
    NumberType => ()
    _ => abort("int() is only valid for number schemas")
  }
  let message = if msg.is_empty() { "Value must be an integer" } else { msg }
  append_rule_with_annotation(
    self,
    fn(json) {
      match json {
        Number(v, ..) => v == v.to_int().to_double()
        _ => false
      }
    },
    message,
    Json::object({ "type": Json::string("integer") }),
  )
}

///|
/// Require the number to be positive (> 0).
pub fn Schema::positive(self : Schema, msg? : String = "") -> Schema {
  match inner_type(self.schema_type) {
    NumberType => ()
    _ => abort("positive() is only valid for number schemas")
  }
  let message = if msg.is_empty() { "Value must be positive" } else { msg }
  append_rule_with_annotation(
    self,
    fn(json) {
      match json {
        Number(v, ..) => v > 0.0
        _ => false
      }
    },
    message,
    Json::object({ "exclusiveMinimum": Json::number(0.0) }),
  )
}

///|
/// Require the number to be negative (< 0).
pub fn Schema::negative(self : Schema, msg? : String = "") -> Schema {
  match inner_type(self.schema_type) {
    NumberType => ()
    _ => abort("negative() is only valid for number schemas")
  }
  let message = if msg.is_empty() { "Value must be negative" } else { msg }
  append_rule_with_annotation(
    self,
    fn(json) {
      match json {
        Number(v, ..) => v < 0.0
        _ => false
      }
    },
    message,
    Json::object({ "exclusiveMaximum": Json::number(0.0) }),
  )
}

///|
/// Require the number to be a multiple of `n`.
pub fn Schema::multipleOf(self : Schema, n : Int, msg? : String = "") -> Schema {
  match inner_type(self.schema_type) {
    NumberType => ()
    _ => abort("multipleOf() is only valid for number schemas")
  }
  let message = if msg.is_empty() {
    "Value must be a multiple of \{n}"
  } else {
    msg
  }
  append_rule_with_annotation(
    self,
    fn(json) {
      match json {
        Number(v, ..) =>
          v / n.to_double() == (v / n.to_double()).to_int().to_double()
        _ => false
      }
    },
    message,
    Json::object({ "multipleOf": Json::number(n.to_double()) }),
  )
}

///|
/// Require the number to be finite (reject Infinity and NaN).
pub fn Schema::finite(self : Schema, msg? : String = "") -> Schema {
  match inner_type(self.schema_type) {
    NumberType => ()
    _ => abort("finite() is only valid for number schemas")
  }
  let message = if msg.is_empty() {
    "Value must be a finite number"
  } else {
    msg
  }
  append_rule(
    self,
    fn(json) {
      match json {
        Number(v, ..) => !v.is_nan() && !v.is_inf()
        _ => false
      }
    },
    message,
  )
}

///|
/// Require the number to be a safe integer (within ±2^53-1).
pub fn Schema::safe(self : Schema, msg? : String = "") -> Schema {
  match inner_type(self.schema_type) {
    NumberType => ()
    _ => abort("safe() is only valid for number schemas")
  }
  let message = if msg.is_empty() {
    "Value must be a safe integer"
  } else {
    msg
  }
  let min_safe = -9007199254740991.0
  let max_safe = 9007199254740991.0
  append_rule(
    self,
    fn(json) {
      match json {
        Number(v, ..) =>
          v == v.to_int().to_double() && v >= min_safe && v <= max_safe
        _ => false
      }
    },
    message,
  )
}