///|
/// JSON Schema 2020-12 认可的实例类型。
/// `JTInteger` 不是独立的 JSON 类型,而是 "number 且没有小数部分" 的约束。
pub(all) enum JsonType {
  JTNull
  JTBoolean
  JTObject
  JTArray
  JTNumber
  JTString
  JTInteger
} derive(Eq)

///|
fn JsonType::parse(s : String, path : String) -> JsonType raise CompileError {
  match s {
    "null" => JTNull
    "boolean" => JTBoolean
    "object" => JTObject
    "array" => JTArray
    "number" => JTNumber
    "string" => JTString
    "integer" => JTInteger
    _ => raise InvalidSchema(path, "invalid type name \"\{s}\"")
  }
}

///|
fn JsonType::label(self : JsonType) -> String {
  match self {
    JTNull => "null"
    JTBoolean => "boolean"
    JTObject => "object"
    JTArray => "array"
    JTNumber => "number"
    JTString => "string"
    JTInteger => "integer"
  }
}

///|
fn Json::json_type(self : Json) -> JsonType {
  match self {
    Null => JTNull
    True | False => JTBoolean
    Object(_) => JTObject
    Array(_) => JTArray
    Number(_) => JTNumber
    String(_) => JTString
  }
}

///|
fn Json::matches_type(self : Json, t : JsonType) -> Bool {
  if t is JTInteger {
    match self {
      Number(n, ..) => !n.is_nan() && n == n.floor()
      _ => false
    }
  } else {
    self.json_type() == t
  }
}

///|
/// 实例校验失败的一条错误。
///
/// - `keyword`:触发错误的关键词,如 `"required"`
/// - `instance_path`:出错数据的位置(RFC 6901 JSON Pointer,如 `"/address/zip"`)
/// - `schema_path`:触发错误的关键词在模式中的位置(如 `"#/properties/age/minimum"`)
/// - `message`:人类可读的错误描述(ajv 风格英文消息)
pub(all) struct ValidationError {
  keyword : String
  instance_path : String
  schema_path : String
  message : String
} derive(Eq, ToJson)

///|
/// 把错误列表渲染为多行文本,适合日志输出与测试快照。
pub fn summarize(errors : Array[ValidationError]) -> String {
  let b = StringBuilder()
  let mut first = true
  for e in errors {
    if !first {
      b.write_string("\n")
    }
    first = false
    let loc = if e.instance_path == "" { "(root)" } else { e.instance_path }
    b.write_string("\{loc}: \{e.message} [\{e.keyword} @ \{e.schema_path}]")
  }
  b.to_string()
}

///|
/// 模式文档本身不合法时抛出的错误(区别于"实例校验失败")。
pub(all) suberror CompileError {
  /// (schema_path, 原因)
  InvalidSchema(String, String)
  /// (schema_path, 关键词名) —— strict 模式下遇到不支持的关键词
  UnsupportedKeyword(String, String)
} derive(Eq)

///|
pub impl Show for CompileError with fn output(self, logger) {
  match self {
    InvalidSchema(path, reason) =>
      logger.write_string("invalid schema at \{path}: \{reason}")
    UnsupportedKeyword(path, kw) =>
      logger.write_string(
        "unsupported keyword \"\{kw}\" at \{path} (strict mode)",
      )
  }
}

///|
/// 校验错误消息的语言(影响实例校验失败的消息文本;
/// 模式编译错误面向开发者,始终为英文)。
pub(all) enum Locale {
  EN
  ZH
} derive(Eq)

///|
/// 按语言挑选消息文案。
fn tr(locale : Locale, en : String, zh : String) -> String {
  match locale {
    EN => en
    ZH => zh
  }
}

///|
/// 编译选项(见 `compile`)。
pub struct CompileOptions {
  /// strict 模式(默认开启,ajv 风格):引擎不支持的关键词直接报错,
  /// 避免拼写错误被静默忽略;`x-` 前缀的扩展关键词始终放行。
  /// 传 `false` 则按规范忽略未知关键词。
  strict : Bool
  /// 按 2020-12 规范,`format` 默认只是 annotation(不断言)。
  /// 打开后对认识的 format(email / uuid / ipv4)进行断言。
  assert_format : Bool
  /// 校验错误消息语言,默认英文。
  locale : Locale
} derive(Eq)

///|
pub fn CompileOptions::new(
  strict? : Bool = true,
  assert_format? : Bool = false,
  locale? : Locale = EN,
) -> CompileOptions {
  { strict, assert_format, locale, }
}

///|
/// 编译上下文:根文档 + `$ref` 惰性编译缓存(支持递归模式)。
struct Ctx {
  root : Json
  strict : Bool
  assert_format : Bool
  locale : Locale
  cache : Map[String, Schema]
}

///|
/// 编译后的模式节点。
enum Node {
  BoolNode(Bool)
  SchemaNode(Keywords)
}

///|
/// 一个模式节点上收集到的全部关键词。
priv struct Keywords {
  mut ref_ : String?
  mut typ : Array[JsonType]?
  mut enum_ : Array[Json]?
  mut const_ : Json?
  // 数值
  mut multiple_of : Double?
  mut maximum : Double?
  mut exclusive_maximum : Double?
  mut minimum : Double?
  mut exclusive_minimum : Double?
  // 字符串
  mut min_length : Int?
  mut max_length : Int?
  mut format : String?
  mut pattern : (@string.Regex, String)?
  // 数组
  mut min_items : Int?
  mut max_items : Int?
  mut unique_items : Bool?
  mut prefix_items : Array[Schema]?
  mut items : Schema?
  mut contains : Schema?
  mut min_contains : Int?
  mut max_contains : Int?
  // 对象
  mut min_properties : Int?
  mut max_properties : Int?
  mut required : Array[String]?
  mut properties : Map[String, Schema]?
  mut pattern_properties : Array[(@string.Regex, String, Schema)]?
  mut property_names : Schema?
  mut additional_closed : Bool?
  mut additional : Schema?
  mut dependent_required : Map[String, Array[String]]?
  mut dependent_schemas : Map[String, Schema]?
  // 跨字段动态规则(引擎扩展关键词 x-rules,路径相对本节点实例)
  mut rule_srcs : Array[String]?
  mut rules : Array[@rules.Expr]?
  // 组合器
  mut all_of : Array[Schema]?
  mut any_of : Array[Schema]?
  mut one_of : Array[Schema]?
  mut not_ : Schema?
  mut if_ : Schema?
  mut then_ : Schema?
  mut else_ : Schema?
}

///|
fn Keywords::empty() -> Keywords {
  {
    ref_: None,
    typ: None,
    enum_: None,
    const_: None,
    multiple_of: None,
    maximum: None,
    exclusive_maximum: None,
    minimum: None,
    exclusive_minimum: None,
    min_length: None,
    max_length: None,
    format: None,
    pattern: None,
    min_items: None,
    max_items: None,
    unique_items: None,
    prefix_items: None,
    items: None,
    contains: None,
    min_contains: None,
    max_contains: None,
    min_properties: None,
    max_properties: None,
    required: None,
    properties: None,
    pattern_properties: None,
    property_names: None,
    additional_closed: None,
    additional: None,
    dependent_required: None,
    dependent_schemas: None,
    rule_srcs: None,
    rules: None,
    all_of: None,
    any_of: None,
    one_of: None,
    not_: None,
    if_: None,
    then_: None,
    else_: None,
  }
}

///|
/// 已编译的 Schema(对应 ajv 的 compiled schema):不可变、可跨请求复用。
pub struct Schema {
  node : Node
  ctx : Ctx
}

///|
/// 校验一个 JSON 实例,返回全部错误;空数组表示通过。
///
/// 对应 ajv 的 `validate` + `errors`:
/// ```moonbit nocheck
/// let v = compile(schema)
/// let errors = v.validate(instance)
/// if errors.length() > 0 { println(summarize(errors)) }
/// ```
pub fn Schema::validate(
  self : Schema,
  instance : Json,
) -> Array[ValidationError] {
  let errors : Array[ValidationError] = []
  let _ = validate_node(self, self.node, instance, [], ["#"], errors)
  errors
}

///|
/// 便捷方法:实例是否通过校验。
pub fn Schema::check(self : Schema, instance : Json) -> Bool {
  self.validate(instance).is_empty()
}

///|
/// 按 JSON Schema 语义的深度相等:数值按值比较(`1` 与 `1.0` 相等),
/// 不依赖解析器保留的字面表示(repr)。
fn json_eq(a : Json, b : Json) -> Bool {
  match (a, b) {
    (Null, Null) => true
    (True, True) => true
    (False, False) => true
    (Number(x, ..), Number(y, ..)) => x == y
    (String(x), String(y)) => x == y
    (Array(x), Array(y)) =>
      if x.length() != y.length() {
        false
      } else {
        let mut same = true
        for i in 0..
      if x.length() != y.length() {
        false
      } else {
        let mut same = true
        for k, v in x {
          match y.get(k) {
            Some(v2) => if !json_eq(v, v2) { same = false }
            None => same = false
          }
        }
        same
      }
    _ => false
  }
}

///|
/// 字符串的 Unicode 码点数。`minLength`/`maxLength` 的规范计量单位是
/// 码点而非 UTF-16 code unit(例如 emoji 是 1 个码点、2 个 UTF-16 单元)。
fn str_code_points(s : String) -> Int {
  let mut n = 0
  for _c in s {
    n += 1
  }
  n
}