///|
/// Construct an owned document. Validation is performed before detaching caller arrays.
pub fn Document::new(
  keys : Array[Key],
  graphs : Array[Graph],
  data~ : Array[Datum] = [],
  description? : String,
  extra~ : Array[XmlAttributeValue] = [],
  namespaces~ : Array[NamespaceBinding] = [],
  limits~ : Limits = Limits::default(),
) -> Document raise GraphError {
  let candidate : Document = {
    keys,
    graphs,
    data,
    description,
    extra,
    namespaces,
  }
  // The bounded exporter/parser pair also validates cycles, extra attributes and XML characters.
  // This is a one-time ownership boundary, not the fast path used by parse().
  validate_model(candidate, limits)
  parse(serialize(candidate, limits~), limits~)
}

///|
/// Return detached graphs. Changes to the returned arrays never mutate the document.
pub fn Document::graphs(self : Document) -> Array[Graph] {
  self.graphs.map(clone_graph)
}

///|
pub fn Document::keys(self : Document) -> Array[Key] {
  self.keys.map(clone_key)
}

///|
pub fn Document::data(self : Document) -> Array[Datum] {
  clone_data(self.data)
}

///|
pub fn Document::description(self : Document) -> String? {
  self.description
}

///|
pub fn Document::extra_attributes(self : Document) -> Array[XmlAttributeValue] {
  self.extra.copy()
}

///|
/// Return a detached root namespace context, including bindings used in extension values.
pub fn Document::namespace_bindings(self : Document) -> Array[NamespaceBinding] {
  self.namespaces.copy()
}

///|
fn clone_xml(values : Array[XmlValue]) -> Array[XmlValue] {
  values.map(fn(value) {
    match value {
      Text(text) => Text(text)
      Element(element) =>
        Element({
          ..element,
          namespaces: element.namespaces.copy(),
          attributes: element.attributes.copy(),
          content: clone_xml(element.content),
        })
    }
  })
}

///|
fn clone_value(value : DataValue) -> DataValue {
  match value {
    ScalarValue(_) => value
    XmlPayload(content) => XmlPayload(clone_xml(content))
  }
}

///|
fn clone_key(key : Key) -> Key {
  {
    ..key,
    extra: key.extra.copy(),
    namespaces: key.namespaces.copy(),
    default_extra: key.default_extra.copy(),
    default_namespaces: key.default_namespaces.copy(),
    default_value: key.default_value.map(clone_value),
  }
}

///|
fn clone_data(data : Array[Datum]) -> Array[Datum] {
  data.map(fn(datum) {
    {
      ..datum,
      value: clone_value(datum.value),
      extra: datum.extra.copy(),
      namespaces: datum.namespaces.copy(),
    }
  })
}

///|
fn clone_locator(locator : Locator) -> Locator {
  {
    ..locator,
    extra: locator.extra.copy(),
    namespaces: locator.namespaces.copy(),
  }
}

///|
fn clone_port(port : Port) -> Port {
  {
    ..port,
    data: clone_data(port.data),
    ports: port.ports.map(clone_port),
    extra: port.extra.copy(),
    namespaces: port.namespaces.copy(),
  }
}

///|
fn clone_node(node : Node) -> Node {
  {
    ..node,
    data: clone_data(node.data),
    ports: node.ports.map(clone_port),
    graph: node.graph.map(clone_graph),
    locator: node.locator.map(clone_locator),
    extra: node.extra.copy(),
    namespaces: node.namespaces.copy(),
  }
}

///|
fn clone_edge(edge : Edge) -> Edge {
  {
    ..edge,
    data: clone_data(edge.data),
    graph: edge.graph.map(clone_graph),
    extra: edge.extra.copy(),
    namespaces: edge.namespaces.copy(),
  }
}

///|
fn clone_hyperedge(hyperedge : Hyperedge) -> Hyperedge {
  {
    ..hyperedge,
    data: clone_data(hyperedge.data),
    endpoints: hyperedge.endpoints.map(fn(endpoint) {
      {
        ..endpoint,
        extra: endpoint.extra.copy(),
        namespaces: endpoint.namespaces.copy(),
      }
    }),
    graph: hyperedge.graph.map(clone_graph),
    extra: hyperedge.extra.copy(),
    namespaces: hyperedge.namespaces.copy(),
  }
}

///|
fn clone_graph(graph : Graph) -> Graph {
  {
    ..graph,
    data: clone_data(graph.data),
    nodes: graph.nodes.map(clone_node),
    edges: graph.edges.map(clone_edge),
    hyperedges: graph.hyperedges.map(clone_hyperedge),
    locator: graph.locator.map(clone_locator),
    extra: graph.extra.copy(),
    namespaces: graph.namespaces.copy(),
  }
}