///|
/// A scoped, owned view of explicit attributes and key defaults.
pub struct Attributes {
  priv scope : Scope
  priv keys : Array[Key]
  priv catalog : Map[String, Key]
  priv explicit : Map[String, Datum]
}

///|
pub fn Attributes::new(
  doc : Document,
  scope : Scope,
  data : Array[Datum],
  limits~ : Limits = Limits::default(),
) -> Attributes raise GraphError {
  require(
    scope != All && scope != EndpointScope,
    "attributes.scope",
    "scope must name an element that accepts data",
  )
  let catalog = key_map(doc.keys)
  check_data(data, scope, catalog)
  let explicit : Map[String, Datum] = Map([])
  limits.validate()
  let output = Output::new(limits)
  output.start("attributes", [], default_ns=GRAPHML_URI)
  write_data(output, data)
  output.end("attributes")
  let owned = read_tree(output.finish(), limits)
  for child in element_children(owned) {
    let datum = read_datum(child, catalog)
    explicit[datum.key] = datum
  }
  { scope, keys: doc.keys, catalog, explicit, }
}

///|
fn Attributes::check_key(
  self : Attributes,
  id : String,
) -> Key raise GraphError {
  let key = match self.catalog.get(id) {
    Some(key) => key
    None =>
      raise InvalidGraph(path="attributes.key", reason="undefined key \{id}")
  }
  require(
    key.scope.applies_to(self.scope),
    "attributes.scope",
    "key scope does not apply",
  )
  key
}

///|
/// Omitted values are resolved from defaults only when requested; explicit empties remain empty.
pub fn Attributes::get(
  self : Attributes,
  id : String,
  use_default~ : Bool = true,
) -> DataValue? raise GraphError {
  let key = self.check_key(id)
  match self.explicit.get(id) {
    Some(datum) => Some(clone_value(datum.value))
    None => if use_default { key.default_value.map(clone_value) } else { None }
  }
}

///|
pub fn Attributes::scalar(
  self : Attributes,
  id : String,
  use_default~ : Bool = true,
) -> Scalar? raise GraphError {
  match self.get(id, use_default~) {
    Some(ScalarValue(value)) => Some(value)
    None => None
    Some(XmlPayload(_)) =>
      raise Unsupported(path="attributes.scalar", feature="extension XML value")
  }
}

///|
/// Attribute names are not necessarily unique. Ambiguity is an error, never an arbitrary choice.
pub fn Attributes::key_by_name(
  self : Attributes,
  name : String,
) -> String? raise GraphError {
  let mut result : String? = None
  for key in self.keys {
    if key.scope.applies_to(self.scope) && key.name == Some(name) {
      require(
        result is None,
        "attributes.name",
        "ambiguous attribute name \{name}",
      )
      result = Some(key.id)
    }
  }
  result
}

///|
pub fn Attributes::contains_explicit(
  self : Attributes,
  id : String,
) -> Bool raise GraphError {
  let _ = self.check_key(id)
  self.explicit.contains(id)
}

///|
/// Resolve in declaration order, keeping explicit data metadata and adding applicable defaults.
pub fn Attributes::resolved(self : Attributes) -> Array[Datum] {
  let result = []
  for key in self.keys {
    if key.scope.applies_to(self.scope) {
      match self.explicit.get(key.id) {
        Some(datum) => result.append(clone_data([datum]))
        None =>
          if key.default_value is Some(value) {
            result.push({
              key: key.id,
              id: None,
              value: clone_value(value),
              extra: [],
              namespaces: key.default_namespaces.copy(),
            })
          }
      }
    }
  }
  result
}