///|
let word_drawing_ns = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"

///|
let picture_ns = "http://schemas.openxmlformats.org/drawingml/2006/picture"

///|
fn word_passive_element(node : Node, parent : String, root : String) -> Bool {
  let ns = node.element.name.namespace_uri.unwrap_or("")
  let name = node.element.name.local_name
  if ns == word_ns {
    match name {
      "document" | "hdr" | "ftr" => node.depth == 0 && name == root
      "body" => parent == "document"
      "p" | "tbl" => ["body", "tc", "hdr", "ftr"].contains(parent)
      "sectPr" => ["body", "pPr"].contains(parent) && root == "document"
      "r" | "pPr" => parent == "p"
      "t" | "rPr" | "br" | "cr" | "tab" | "drawing" => parent == "r"
      "b" | "i" | "u" | "sz" | "szCs" | "rFonts" | "lang" | "color" =>
        parent == "rPr"
      "spacing"
      | "jc"
      | "ind"
      | "pStyle"
      | "keepNext"
      | "keepLines"
      | "widowControl" => parent == "pPr" || (name == "jc" && parent == "tblPr")
      "pgSz"
      | "pgMar"
      | "cols"
      | "docGrid"
      | "type"
      | "titlePg"
      | "headerReference"
      | "footerReference" => parent == "sectPr"
      "tblPr" | "tblGrid" | "tr" => parent == "tbl"
      "tblW"
      | "tblLayout"
      | "tblStyle"
      | "tblLook"
      | "tblInd"
      | "tblBorders"
      | "tblCellMar" => parent == "tblPr"
      "gridCol" => parent == "tblGrid"
      "trPr" | "tc" => parent == "tr"
      "trHeight" | "tblHeader" | "cantSplit" => parent == "trPr"
      "tcPr" => parent == "tc"
      "tcW" | "gridSpan" | "vMerge" | "vAlign" | "tcBorders" | "tcMar" =>
        parent == "tcPr"
      "shd" => ["tcPr", "tblPr", "pPr", "rPr"].contains(parent)
      "top" | "left" | "bottom" | "right" | "insideH" | "insideV" =>
        ["tblBorders", "tcBorders", "tblCellMar", "tcMar"].contains(parent)
      _ => false
    }
  } else if ns == word_drawing_ns {
    match name {
      "inline" => parent == "drawing"
      "extent" | "effectExtent" | "docPr" | "cNvGraphicFramePr" =>
        parent == "inline"
      _ => false
    }
  } else if ns == picture_ns {
    match name {
      "pic" => parent == "graphicData"
      "nvPicPr" | "blipFill" | "spPr" => parent == "pic"
      "cNvPr" | "cNvPicPr" => parent == "nvPicPr"
      _ => false
    }
  } else if ns == drawing_ns {
    match name {
      "graphic" => parent == "inline"
      "graphicData" => parent == "graphic"
      "blip" | "stretch" => parent == "blipFill"
      "fillRect" => parent == "stretch"
      "xfrm" | "prstGeom" => parent == "spPr"
      "off" | "ext" => parent == "xfrm"
      "avLst" => parent == "prstGeom"
      "graphicFrameLocks" => parent == "cNvGraphicFramePr"
      "picLocks" => parent == "cNvPicPr"
      _ => false
    }
  } else {
    false
  }
}

///|
fn passive_drawing_attribute(
  ns : String,
  element : String,
  name : String,
) -> Bool {
  if ns == word_drawing_ns {
    match element {
      "inline" => ["distT", "distB", "distL", "distR"].contains(name)
      "extent" => ["cx", "cy"].contains(name)
      "effectExtent" => ["l", "t", "r", "b"].contains(name)
      "docPr" => ["id", "name", "descr", "title", "hidden"].contains(name)
      _ => false
    }
  } else if ns == picture_ns {
    element == "cNvPr" &&
    ["id", "name", "descr", "title", "hidden"].contains(name)
  } else {
    match element {
      "graphicData" => name == "uri"
      "blip" => name == "cstate"
      "xfrm" => ["rot", "flipH", "flipV"].contains(name)
      "off" => ["x", "y"].contains(name)
      "ext" => ["cx", "cy"].contains(name)
      "prstGeom" => name == "prst"
      "graphicFrameLocks" | "picLocks" =>
        ["noChangeAspect", "noChangeArrowheads"].contains(name)
      _ => false
    }
  }
}

///|
fn validate_word_reference_attribute(
  pkg : Package,
  part : PartRecord,
  node : Node,
  attribute : @xml.NamespaceAttribute,
) -> Unit raise {
  let ns = node.element.name.namespace_uri.unwrap_or("")
  let name = node.element.name.local_name
  let kind = if ns == drawing_ns &&
    name == "blip" &&
    attribute.name.local_name == "embed" {
    office_rel + "image"
  } else if ns == word_ns &&
    ["headerReference", "footerReference"].contains(name) &&
    attribute.name.local_name == "id" {
    office_rel + (if name == "headerReference" { "header" } else { "footer" })
  } else if ns == word_ns &&
    name == "attachedTemplate" &&
    attribute.name.local_name == "id" &&
    part.content_type.contains(".settings+") {
    office_rel + "attachedTemplate"
  } else {
    raise Refused("unsupported Word relationship attribute context")
  }
  guard graph_relationship(pkg.graph, part.name, attribute.value) is Some(r) &&
    r.kind == kind &&
    (!r.external || is_template_relationship(r)) else {
    raise Invalid("Word XML reference is bound to the wrong relationship kind")
  }
}

///|
fn validate_word_references(pkg : Package) -> Unit raise {
  let referenced : Map[String, Bool] = Map([])
  for reference in pkg.graph.graph.xml_references {
    referenced[reference.source + "#" + reference.relationship_id] = true
  }
  for r in pkg.audit.relationships {
    if [office_rel + "header", office_rel + "footer", office_rel + "image"].contains(
        r.kind,
      ) {
      guard referenced.contains(r.source + "#" + r.id) else {
        raise Invalid(
          "Word content relationship is not referenced by its source",
        )
      }
    }
  }
}