///|
pub struct BrandDocumentResult {
  value : BrandBook?
  diagnostics : DiagnosticBag
} derive(Debug, Eq)

///|
pub enum DocumentFormat {
  Auto
  Json
  Yaml
} derive(Debug, Eq)

///|
pub fn DocumentFormat::auto() -> DocumentFormat {
  Auto
}

///|
pub fn DocumentFormat::json() -> DocumentFormat {
  Json
}

///|
pub fn DocumentFormat::yaml() -> DocumentFormat {
  Yaml
}

///|
fn add_document_error(
  diagnostics : Array[Diagnostic],
  path : String,
  message : String,
) -> Unit {
  diagnostics.push(
    Diagnostic::error(path, message, Some(SourceLocation::new(1, 1, 0))),
  )
}

///|
fn get_object(
  value : JsonValue?,
  path : String,
  diagnostics : Array[Diagnostic],
) -> JsonValue? {
  match value {
    Some(JObject(_)) => value
    Some(_) => {
      add_document_error(diagnostics, path, "expected an object")
      None
    }
    None => {
      add_document_error(diagnostics, path, "required object is missing")
      None
    }
  }
}

///|
fn get_array(
  value : JsonValue?,
  path : String,
  diagnostics : Array[Diagnostic],
) -> Array[JsonValue] {
  match value {
    Some(JArray(items)) => items
    Some(_) => {
      add_document_error(diagnostics, path, "expected an array")
      []
    }
    None => []
  }
}

///|
fn get_required_string(
  object : JsonValue,
  key : String,
  path : String,
  diagnostics : Array[Diagnostic],
) -> String {
  match object.object_field(key) {
    Some(JString(value)) => value
    Some(_) => {
      add_document_error(diagnostics, path, "expected a string")
      ""
    }
    None => {
      add_document_error(diagnostics, path, "required string is missing")
      ""
    }
  }
}

///|
fn get_optional_string(
  object : JsonValue,
  key : String,
  fallback : String,
) -> String {
  match object.object_field(key) {
    Some(JString(value)) => value
    _ => fallback
  }
}

///|
fn get_int(
  object : JsonValue,
  key : String,
  path : String,
  diagnostics : Array[Diagnostic],
  required : Bool,
) -> Int {
  match object.object_field(key) {
    Some(JNumber(value)) =>
      match parse_decimal_int(value) {
        Some(number) => number
        None => {
          add_document_error(diagnostics, path, "expected an integer")
          0
        }
      }
    Some(_) => {
      add_document_error(diagnostics, path, "expected a number")
      0
    }
    None => {
      if required {
        add_document_error(diagnostics, path, "required number is missing")
      }
      0
    }
  }
}

///|
fn parse_decimal_int(text : String) -> Int? {
  if text == "" {
    return None
  }
  let mut sign = 1
  let mut start = 0
  if text[0] == '-' {
    sign = -1
    start = 1
  } else if text[0] == '+' {
    start = 1
  }
  if start >= text.length() {
    return None
  }
  let mut value = 0
  for i in start.. '9' {
      return None
    }
    value = value * 10 + ch.to_int() - '0'.to_int()
  }
  Some(value * sign)
}