///|
/// Shared checked values for OpenRPC documents and embedded schemas.
pub struct ComponentKey {
  text : String
} derive(Eq, Debug)

///|
pub fn ComponentKey::parse(
  text : String,
) -> Result[ComponentKey, @exact.Diagnostic] {
  if text.is_empty() ||
    !text
    .code_units()
    .iter()
    .all(c => {
      (c >= 48 && c <= 57) ||
      (c >= 65 && c <= 90) ||
      (c >= 97 && c <= 122) ||
      c == 45 ||
      c == 46 ||
      c == 95
    }) {
    Err(
      @exact.Diagnostic::new(
        @exact.WrongType,
        "Component keys must match ^[a-zA-Z0-9.\\-_]+$",
      ),
    )
  } else {
    Ok({ text, })
  }
}

///|
/// Retain a component key exactly as read. Whether it follows a version's key
/// grammar is a validation result, not a reading failure.
pub fn ComponentKey::from_wire(text : String) -> ComponentKey {
  { text, }
}

///|
/// Whether the key matches `^[a-zA-Z0-9.\-_]+$`.
pub fn ComponentKey::is_conforming(self : ComponentKey) -> Bool {
  ComponentKey::parse(self.text) is Ok(_)
}

///|
pub fn ComponentKey::to_string(self : ComponentKey) -> String {
  self.text
}

///|
pub struct ExtensionName {
  text : String
} derive(Eq, Debug)

///|
pub fn ExtensionName::parse(
  text : String,
) -> Result[ExtensionName, @exact.Diagnostic] {
  if text.has_prefix("x-") {
    Ok({ text, })
  } else {
    Err(
      @exact.Diagnostic::new(
        @exact.WrongType,
        "Extension names must start with x-",
      ),
    )
  }
}

///|
pub fn ExtensionName::to_string(self : ExtensionName) -> String {
  self.text
}

///|
pub struct ErrorCode {
  number : @exact.Number
} derive(Eq, Debug)

///|
pub fn ErrorCode::from_number(
  number : @exact.Number,
) -> Result[ErrorCode, @exact.Diagnostic] {
  if number.is_integer() {
    Ok({ number, })
  } else {
    Err(
      @exact.Diagnostic::new(
        @exact.WrongType,
        "Error code must be a mathematical integer",
      ),
    )
  }
}

///|
pub fn ErrorCode::parse(token : String) -> Result[ErrorCode, @exact.Diagnostic] {
  match @exact.Number::parse(token) {
    Err(d) => Err(d)
    Ok(number) => ErrorCode::from_number(number)
  }
}

///|
pub fn ErrorCode::number(self : ErrorCode) -> @exact.Number {
  self.number
}

///|
pub struct Extensions {
  entries : Array[(ExtensionName, @exact.Value)]
} derive(Debug)

///|
pub fn Extensions::from_json(
  value : @exact.Value,
) -> Result[Extensions, @exact.Diagnostic] {
  guard value.kind() is @exact.Object(fields) else {
    return Err(
      @exact.Diagnostic::new(@exact.WrongType, "Extensions require an object"),
    )
  }
  let entries = []
  for (key, value) in fields {
    match ExtensionName::parse(key) {
      Ok(name) => entries.push((name, value))
      Err(d) =>
        return Err(@exact.Diagnostic::new(d.code(), d.message(), path=[key]))
    }
  }
  Ok({ entries, })
}

///|
pub fn Extensions::entries(
  self : Extensions,
) -> Array[(ExtensionName, @exact.Value)] {
  self.entries.copy()
}

///|
pub fn Extensions::get(
  self : Extensions,
  name : ExtensionName,
) -> @exact.Value? {
  for (key, value) in self.entries {
    if key == name {
      return Some(value)
    }
  }
  None
}

///|
pub fn Extensions::to_json(self : Extensions) -> @exact.Value {
  @exact.Value::object(self.entries.map(entry => (entry.0.text, entry.1))).unwrap()
}

///|
pub fn Extensions::empty() -> Extensions {
  { entries: [], }
}

///|
pub fn Extensions::from_entries(
  entries : Array[(ExtensionName, @exact.Value)],
) -> Result[Extensions, @exact.Diagnostic] {
  match @exact.Value::object(entries.map(entry => (entry.0.text, entry.1))) {
    Err(d) => Err(d)
    Ok(_) => Ok({ entries: entries.copy(), })
  }
}

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

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

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

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

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

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

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