///|
fn is_template_relationship(r : Relationship) -> Bool {
  r.external && r.kind == office_rel + "attachedTemplate"
}

///|
fn validate_template_relationship(
  pkg : Package,
  r : Relationship,
) -> Unit raise {
  guard is_template_relationship(r) &&
    (r.target.has_prefix("https://") || r.target.has_prefix("http://")) &&
    pkg.graph.part_index.get(r.source) is Some(i) &&
    pkg.audit.parts[i].content_type ==
    "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml" else {
    raise Refused(
      "only settings-local HTTP(S) attached templates can be removed",
    )
  }
  let references = pkg.graph.graph.xml_references.filter(fn(reference) {
    reference.source == r.source && reference.relationship_id == r.id
  })
  guard references.length() == 1 &&
    references[0].element_namespace == word_ns &&
    references[0].element_name == "attachedTemplate" &&
    references[0].depth == 1 else {
    raise Invalid(
      "external template must have exactly one settings-local reference",
    )
  }
}

///|
fn is_template_element(element : @xml.NamespaceElement) -> Bool {
  element.name.namespace_uri == Some(word_ns) &&
  element.name.local_name == "attachedTemplate"
}

///|
// Namespace-aware selection, raw event writing: preserve all other settings,
// text, comments and namespace declarations. No network dereference occurs.
fn write_template_settings(pkg : Package, part : String) -> Bytes raise {
  let text = @utf8.decode(get_part(pkg.archive, part))
  let raw = @xml.Reader::from_string(text)
  let semantic = @xml.NamespaceReader::from_string(text)
  let writer = @xml.Writer::new()
  let mut skipped = 0
  let mut removed = 0
  while true {
    let event = raw.read_event()
    let meaning = semantic.read_event()
    let omit = match meaning.kind {
      Start(element) =>
        if skipped > 0 {
          skipped += 1
          true
        } else if is_template_element(element) {
          skipped = 1
          removed += 1
          true
        } else {
          false
        }
      End(_) =>
        if skipped > 0 {
          skipped -= 1
          true
        } else {
          false
        }
      Empty(element) =>
        if skipped > 0 {
          true
        } else if is_template_element(element) {
          removed += 1
          true
        } else {
          false
        }
      Eof => break
      _ => skipped > 0
    }
    if !omit {
      writer.write_event(event)
    }
  }
  guard removed == 1 && skipped == 0 else {
    raise Invalid(
      "template rewrite did not remove exactly one validated element",
    )
  }
  @utf8.encode(writer.to_string()[:])
}

///|
fn template_rewritten(pkg : Package, part : String) -> Bool {
  pkg.audit.relationships.any(fn(r) {
    r.source == part && is_template_relationship(r)
  })
}

///|
fn verify_template_semantics(
  before : Package,
  after : Package,
  part : String,
) -> Unit raise {
  let original = before.xml_nodes[part].filter(fn(node) {
    !is_template_element(node.element)
  })
  let actual = after.xml_nodes[part]
  guard original.length() == actual.length() else {
    raise Invalid("template rewrite changed unrelated settings structure")
  }
  for i, node in original {
    let left = node.element
    let right = actual[i].element
    guard node.depth == actual[i].depth &&
      left.name.namespace_uri == right.name.namespace_uri &&
      left.name.local_name == right.name.local_name &&
      left.attributes.length() == right.attributes.length() &&
      left.attributes.all(fn(a) {
        right.attributes.any(fn(b) {
          a.name.namespace_uri == b.name.namespace_uri &&
          a.name.local_name == b.name.local_name &&
          a.value == b.value
        })
      }) &&
      node.text_fragments == actual[i].text_fragments else {
      raise Invalid("template rewrite changed unrelated settings semantics")
    }
  }
  guard !after.audit.relationships.any(is_template_relationship) else {
    raise Invalid("external attached template retained")
  }
}