///|
/// Output layout. `Compact` is canonical; `Pretty` adds two-space indentation
/// and changes nothing else.
pub(all) enum Format {
  Compact
  Pretty
} derive(Eq, Debug)

///|
pub extend Format with Eq::{equal, not_equal}

///|
pub extend Format with @debug.Debug::{to_repr}

///|
pub(all) enum Code {
  Syntax
  DuplicateKey
  LimitExceeded
  InvalidNumber
  InvalidReference
  UnresolvedReference
  WrongType
  MissingField
  UnsupportedVersion
  UnsupportedDialect
  UnsupportedCheck
  InvalidSchema
  ParameterOrder
  MissingBaseUri
  UnavailableResource
  ConflictingIdentifier
  ReferenceCycle
  DuplicateName
  DuplicateErrorCode
  UnknownMethod
  UnknownField
  InvalidValue
  StaleNode
} derive(Eq, Debug)

///|
pub struct Span {
  start : Int32
  end : Int32
} derive(Eq, Debug)

///|
pub fn Span::new(start : Int32, end : Int32) -> Span? {
  if start >= 0 && end >= start {
    Some({ start, end, })
  } else {
    None
  }
}

///|
pub fn Span::start(self : Span) -> Int32 {
  self.start
}

///|
pub fn Span::end(self : Span) -> Int32 {
  self.end
}

///|
pub struct Diagnostic {
  code : Code
  message : String
  path : Array[String]
  span : Span?
  related : Array[Span]
} derive(Debug)

///|
pub fn Diagnostic::new(
  code : Code,
  message : String,
  path~ : Array[String] = [],
  span~ : Span? = None,
  related~ : Array[Span] = [],
) -> Diagnostic {
  { code, message, path: path.copy(), span, related: related.copy(), }
}

///|
pub fn Diagnostic::code(self : Diagnostic) -> Code {
  self.code
}

///|
pub fn Diagnostic::message(self : Diagnostic) -> String {
  self.message
}

///|
pub fn Diagnostic::path(self : Diagnostic) -> Array[String] {
  self.path.copy()
}

///|
pub fn Diagnostic::span(self : Diagnostic) -> Span? {
  self.span
}

///|
pub fn Diagnostic::related(self : Diagnostic) -> Array[Span] {
  self.related.copy()
}

///|
pub struct Limits {
  max_depth : Int32
  max_nodes : Int32
  max_source : Int32
  max_number : Int32
  max_findings : Int32
} derive(Debug)

///|
pub fn Limits::new(
  max_depth~ : Int32 = 256,
  max_nodes~ : Int32 = 100000,
  max_source~ : Int32 = 8388608,
  max_number~ : Int32 = 4096,
  max_findings~ : Int32 = 256,
) -> Limits {
  { max_depth, max_nodes, max_source, max_number, max_findings, }
}

///|
/// Remove policy bounds within the maximum representable String/Array sizes.
/// This does not guarantee available host memory.
pub fn Limits::unlimited() -> Limits {
  Limits::new(
    max_depth=2147483647,
    max_nodes=2147483647,
    max_source=2147483647,
    max_number=2147483647,
    max_findings=2147483647,
  )
}

///|
pub struct Number {
  token : String
} derive(Eq, Debug)

///|
pub fn Number::token(self : Number) -> String {
  self.token
}

///|
pub(all) enum Kind {
  Null
  Boolean(Bool)
  String(String)
  Numeric(Number)
  Array(Array[Value])
  Object(Array[(String, Value)])
} derive(Debug)

///|
pub struct Value {
  priv kind : Kind
} derive(Debug)

///|
pub fn Value::kind(self : Value) -> Kind {
  match self.kind {
    Array(items) => Array(items.copy())
    Object(fields) => Object(fields.copy())
    other => other
  }
}

///|
pub fn Value::null() -> Value {
  { kind: Null, }
}

///|
pub fn Value::boolean(value : Bool) -> Value {
  { kind: Boolean(value), }
}

///|
pub fn Value::string(value : String) -> Value {
  { kind: String(value), }
}

///|
pub fn Value::number(value : Number) -> Value {
  { kind: Numeric(value), }
}

///|
pub fn Value::array(items : Array[Value]) -> Value {
  { kind: Array(items.copy()), }
}

///|
pub fn Value::object(
  fields : Array[(String, Value)],
) -> Result[Value, Diagnostic] {
  let names : Set[String] = Set([])
  for (name, _) in fields {
    if names.contains(name) {
      return Err(
        Diagnostic::new(DuplicateKey, "Duplicate object member", path=[name]),
      )
    }
    names.add(name)
  }
  Ok({ kind: Object(fields.copy()), })
}

///|
pub fn Value::get(self : Value, name : String) -> Value? {
  match self.kind {
    Object(fields) => {
      for (key, value) in fields {
        if key == name {
          return Some(value)
        }
      }
      None
    }
    _ => None
  }
}

///|
pub fn Value::text(self : Value) -> String? {
  match self.kind {
    String(text) => Some(text)
    _ => None
  }
}

///|
/// Inspect array size without creating a copied collection view.
pub fn Value::array_length(self : Value) -> Int32? {
  match self.kind {
    Array(items) => Some(items.length())
    _ => None
  }
}

///|
/// Return an immutable array child, or None for a non-array or missing index.
pub fn Value::array_item(self : Value, index : Int32) -> Value? {
  match self.kind {
    Array(items) =>
      if index >= 0 && index < items.length() {
        Some(items[index])
      } else {
        None
      }
    _ => None
  }
}

///|
pub extend Code with Eq::{equal, not_equal}

///|
pub extend Code with @debug.Debug::{to_repr}

///|
pub extend Span with Eq::{equal, not_equal}

///|
pub extend Span with @debug.Debug::{to_repr}

///|
pub extend Diagnostic with @debug.Debug::{to_repr}

///|
pub extend Limits with @debug.Debug::{to_repr}

///|
pub extend Number with Eq::{equal, not_equal}

///|
pub extend Number with @debug.Debug::{to_repr}

///|
pub extend Kind with @debug.Debug::{to_repr}

///|
pub extend Value with @debug.Debug::{to_repr}