///|
/// A format-checking function: whether the instance conforms. It may raise
/// to signal non-conformance (see `FormatChecker::checks`'s `raises`).
pub type FormatCheckFn = (Json) -> Bool raise

///|
/// A registered format check: the function, and which errors it raises to
/// signal an invalid instance (upstream's `raises` exception types).
pub struct FormatCheck {
  func : FormatCheckFn
  raises : (Error) -> Bool
}

///|
/// A `format` property checker (`jsonschema.FormatChecker`).
///
/// JSON Schema does not mandate that the `format` property actually do any
/// validation. If validation is desired however, instances of this type can
/// be hooked into validators to enable format validation.
///
/// `FormatChecker`s always return `true` when asked about formats that they
/// do not know how to validate.
pub struct FormatChecker {
  checkers : Map[String, FormatCheck]
}

///|
fn raises_nothing(_ : Error) -> Bool {
  false
}

///|
/// Upstream's (deprecated, global) class-level registry
/// `FormatChecker.checkers`, which `FormatChecker::new()` copies.
let class_checkers : Map[String, FormatCheck] = Map([])

///|
/// `FormatChecker(formats)`: a checker for the given formats, taken from the
/// class-level registry (all of them when `formats` is omitted).
///
/// Raises `UnknownFormat` (upstream: `KeyError`) for formats not in the
/// registry.
pub fn FormatChecker::new(
  formats? : ArrayView[String],
) -> FormatChecker raise UnknownFormat {
  let checkers = Map([])
  match formats {
    None =>
      for k, v in class_checkers {
        checkers[k] = v
      }
    Some(formats) =>
      for k in formats {
        match class_checkers.get(k) {
          Some(v) => checkers[k] = v
          None => raise UnknownFormat(k)
        }
      }
  }
  { checkers, }
}

///|
/// Raised by `FormatChecker::new` for unknown formats (upstream: `KeyError`).
pub suberror UnknownFormat {
  UnknownFormat(String)
} derive(Debug)

///|
/// `repr(checker)`: ``.
pub impl Show for FormatChecker with fn output(self, logger) {
  let names = self.checkers.keys().collect()
  names.sort_by(py_str_compare)
  logger.write_string(" @pycompat.repr_string(n)).join(", "))
  logger.write_string("]>")
}

///|
/// Register `func` as validating a new format.
///
/// `raises` decides which errors raised by `func` signal an invalid
/// instance; such an error becomes the `cause` of the resulting
/// `FormatError` (and thereby of the `ValidationError`).
pub fn FormatChecker::checks(
  self : FormatChecker,
  format : String,
  func : FormatCheckFn,
  raises? : (Error) -> Bool = raises_nothing,
) -> Unit {
  self.checkers[format] = { func, raises, }
}

///|
/// Upstream's deprecated `FormatChecker.cls_checks`: register a check in the
/// class-level registry copied by future `FormatChecker::new()` calls.
pub fn FormatChecker::cls_checks(
  format : String,
  func : FormatCheckFn,
  raises? : (Error) -> Bool = raises_nothing,
) -> Unit {
  class_checkers[format] = { func, raises, }
}

///|
/// Check whether the instance conforms to the given format.
///
/// Raises `FormatError` if it does not; errors raised by the check that are
/// not covered by its `raises` propagate unchanged.
pub fn FormatChecker::check(
  self : FormatChecker,
  instance : Json,
  format : String,
) -> Unit raise {
  guard self.checkers.get(format) is Some({ func, raises, }) else { return }
  let mut cause : Error? = None
  let result = func(instance) catch {
    e =>
      if raises(e) {
        cause = Some(e)
        false
      } else {
        raise e
      }
  }
  if !result {
    raise FormatError(
      message="\{@pycompat.repr(instance)} is not a \{@pycompat.repr_string(format)}",
      cause~,
    )
  }
}

///|
/// Whether the instance conforms to the given format.
pub fn FormatChecker::conforms(
  self : FormatChecker,
  instance : Json,
  format : String,
) -> Bool raise {
  self.check(instance, format) catch {
    FormatError(_) => return false
    e => raise e
  }
  true
}