///|
/// The key of one member occurrence: an object member name or an array position.
pub(all) enum Key {
  Name(String)
  Index(Int)
} derive(Eq, Debug)

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

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

///|
/// One member occurrence of a container. `value` names the child node.
/// Repeated names and equal values keep separate member identities.
pub struct Member {
  id : String
  key : Key
  value : String
  source : @diagrams.Span?
} derive(Eq, Debug)

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

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

///|
pub fn Member::named(
  id : String,
  name : String,
  value : String,
  source? : @diagrams.Span,
) -> Member {
  { id, key: Name(name), value, source }
}

///|
pub fn Member::index(
  id : String,
  index : Int,
  value : String,
  source? : @diagrams.Span,
) -> Member {
  { id, key: Index(index), value, source }
}

///|
/// A typed value. Numbers keep their exact decimal lexeme; no machine
/// floating-point value is computed.
pub(all) enum Value {
  Object(ReadOnlyArray[Member])
  Array(ReadOnlyArray[Member])
  Text(String)
  Number(String)
  Boolean(Bool)
  Null
} derive(Eq, Debug)

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

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

///|
pub struct Node {
  id : String
  value : Value
  source : @diagrams.Span?
} derive(Eq, Debug)

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

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

///|
pub fn Node::new(id : String, value : Value, source? : @diagrams.Span) -> Node {
  { id, value, source }
}

///|
pub fn Node::object(
  id : String,
  members : ArrayView[Member],
  source? : @diagrams.Span,
) -> Node {
  { id, value: Object(ReadOnlyArray::from_array(members)), source }
}

///|
pub fn Node::array(
  id : String,
  members : ArrayView[Member],
  source? : @diagrams.Span,
) -> Node {
  { id, value: Array(ReadOnlyArray::from_array(members)), source }
}

///|
/// The member occurrences of a container, or an empty array for a scalar.
pub fn Node::members(self : Node) -> ReadOnlyArray[Member] {
  match self.value {
    Object(members) | Array(members) => members
    _ => []
  }
}

///|
pub fn Node::is_container(self : Node) -> Bool {
  self.value is (Object(_) | Array(_))
}

///|
/// A view highlight. Targets are member or node identities. The optional
/// style is inert author metadata; library themes paint every highlight.
pub struct Highlight {
  targets : ReadOnlyArray[String]
  style : String?
} derive(Eq, Debug)

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

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

///|
pub fn Highlight::new(targets : ArrayView[String], style? : String) -> Highlight {
  { targets: ReadOnlyArray::from_array(targets), style }
}

///|
/// One immutable structured-data diagram. It does not depend on JSON or YAML.
pub struct DataTree {
  id : String
  root : String
  nodes : ReadOnlyArray[Node]
  highlights : ReadOnlyArray[Highlight]
  title : String?
  accessibility_title : String?
  accessibility_description : String?
} derive(Eq, Debug)

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

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

///|
pub fn DataTree::new(
  id : String,
  root : String,
  nodes : ArrayView[Node],
  highlights? : ArrayView[Highlight] = [],
  title? : String,
  accessibility_title? : String,
  accessibility_description? : String,
) -> DataTree {
  {
    id,
    root,
    nodes: ReadOnlyArray::from_array(nodes),
    highlights: ReadOnlyArray::from_array(highlights),
    title,
    accessibility_title,
    accessibility_description,
  }
}

///|
/// The visible text of a scalar value, or `None` for a container.
pub fn Value::scalar_text(self : Value) -> String? {
  match self {
    Text(text) => Some(text)
    Number(lexeme) => Some(lexeme)
    Boolean(true) => Some("true")
    Boolean(false) => Some("false")
    Null => Some("null")
    _ => None
  }
}

///|
/// True when `lexeme` is a valid JSON number spelling.
pub fn valid_number(lexeme : String) -> Bool {
  let n = lexeme.length()
  let mut i = 0
  fn digit(at : Int) -> Bool {
    at < n && lexeme.code_unit_at(at) >= '0'.to_int().to_uint16() &&
    lexeme.code_unit_at(at) <= '9'.to_int().to_uint16()
  }
  fn at(position : Int, c : Char) -> Bool {
    position < n && lexeme.code_unit_at(position).to_int() == c.to_int()
  }
  if at(i, '-') {
    i += 1
  }
  if at(i, '0') {
    i += 1
  } else if digit(i) {
    while digit(i) {
      i += 1
    }
  } else {
    return false
  }
  if at(i, '.') {
    i += 1
    if !digit(i) {
      return false
    }
    while digit(i) {
      i += 1
    }
  }
  if at(i, 'e') || at(i, 'E') {
    i += 1
    if at(i, '+') || at(i, '-') {
      i += 1
    }
    if !digit(i) {
      return false
    }
    while digit(i) {
      i += 1
    }
  }
  i == n
}