///|
pub(all) enum Scope {
  All
  DocumentScope
  GraphScope
  NodeScope
  EdgeScope
  PortScope
  HyperedgeScope
  EndpointScope
} derive(Eq, @debug.Debug)

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

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

///|
pub fn Scope::from_name(name : String) -> Scope raise GraphError {
  match name {
    "all" => All
    "graphml" => DocumentScope
    "graph" => GraphScope
    "node" => NodeScope
    "edge" => EdgeScope
    "port" => PortScope
    "hyperedge" => HyperedgeScope
    "endpoint" => EndpointScope
    _ => raise InvalidGraph(path="key.for", reason="unknown scope \{name}")
  }
}

///|
pub fn Scope::name(self : Scope) -> String {
  match self {
    All => "all"
    DocumentScope => "graphml"
    GraphScope => "graph"
    NodeScope => "node"
    EdgeScope => "edge"
    PortScope => "port"
    HyperedgeScope => "hyperedge"
    EndpointScope => "endpoint"
  }
}

///|
pub fn Scope::applies_to(self : Scope, domain : Scope) -> Bool {
  self == All || self == domain
}

///|
pub(all) struct Key {
  id : String
  scope : Scope
  name : String?
  value_type : ValueType?
  default_value : DataValue?
  default_extra : Array[XmlAttributeValue]
  default_namespaces : Array[NamespaceBinding]
  description : String?
  extra : Array[XmlAttributeValue]
  namespaces : Array[NamespaceBinding]
}

///|
pub fn Key::new(
  id : String,
  scope : Scope,
  value_type : ValueType,
  name? : String,
  default_value? : Scalar,
  description? : String,
) -> Key raise GraphError {
  valid_token(id, "key.id")
  if name is Some(value) {
    valid_token(value, "key.attr.name")
  }
  if default_value is Some(value) {
    require(
      value.value_type() == value_type,
      "key.default",
      "type does not match key",
    )
  }
  {
    id,
    scope,
    name,
    value_type: Some(value_type),
    default_value: default_value.map(fn(value) { ScalarValue(value) }),
    default_extra: [],
    default_namespaces: [],
    description,
    extra: [],
    namespaces: [],
  }
}

///|
fn valid_token(value : String, path : String) -> Unit raise GraphError {
  let _ = utf8_size(value)
  require(value.length() > 0, path, "empty NMTOKEN")
  // XML 1.0 Fifth Edition NameChar ranges; NMTOKEN may begin with a digit.
  for ch in value {
    require(xml_name_char(ch.to_int()), path, "invalid NMTOKEN character")
  }
}

///|
fn xml_name_char(c : Int32) -> Bool {
  let ascii_name = (c >= 65 && c <= 90) ||
    (c >= 97 && c <= 122) ||
    (c >= 48 && c <= 57) ||
    c == 95 ||
    c == 58 ||
    c == 45 ||
    c == 46
  let unicode_name = (c >= 0xC0 && c <= 0xD6) ||
    (c >= 0xD8 && c <= 0xF6) ||
    (c >= 0xF8 && c <= 0x2FF) ||
    (c >= 0x370 && c <= 0x37D) ||
    (c >= 0x37F && c <= 0x1FFF) ||
    (c >= 0x200C && c <= 0x200D) ||
    (c >= 0x2070 && c <= 0x218F) ||
    (c >= 0x2C00 && c <= 0x2FEF) ||
    (c >= 0x3001 && c <= 0xD7FF) ||
    (c >= 0xF900 && c <= 0xFDCF) ||
    (c >= 0xFDF0 && c <= 0xFFFD) ||
    (c >= 0x10000 && c <= 0xEFFFF) ||
    c == 0xB7 ||
    (c >= 0x300 && c <= 0x36F) ||
    (c >= 0x203F && c <= 0x2040)
  ascii_name || unicode_name
}