// Package-manifest structural/identity checks; not a full RNG validator.

///|
let read_manifest = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#"

///|
fn check_package_manifest(
  xml : String,
  names : Array[String],
  id_assignments~ : Array[XmlIdAssignment] = [],
  id_diagnostics~ : Array[XmlIdDiagnostic] = [],
  processor_events~ : Array[XmlProcessorEvent] = [],
) -> Unit raise OdsError {
  let root = read_xml(
    xml,
    document_part="META-INF/manifest.xml",
    id_assignments~,
    id_diagnostics~,
    processor_events~,
  )
  if root.name != read_manifest + "manifest" {
    raise Invalid("expected ODF manifest root")
  }
  let entries : Map[String, String] = Map([])
  for entry in root.elements() {
    if entry.name == read_manifest + "encrypted-key" {
      raise Invalid("encrypted ODF package key material is unsupported")
    }
    if entry.name != read_manifest + "file-entry" {
      if !entry.name.has_prefix(read_manifest) {
        manifest_extension_event(processor_events, entry.name)
        continue
      }
      raise Invalid("unsupported manifest element: \{entry.name}")
    }
    let path = match entry.attrs.get(read_manifest + "full-path") {
      Some(p) => p
      None => raise Invalid("manifest entry missing full-path")
    }
    let mime = match entry.attrs.get(read_manifest + "media-type") {
      Some(m) => m
      None => raise Invalid("manifest entry missing media-type")
    }
    if entries.contains(path) {
      raise Invalid("duplicate manifest full-path")
    }
    if path == "" || path == "mimetype" || path == "META-INF/manifest.xml" {
      raise Invalid("forbidden manifest full-path")
    }
    if path != "/" {
      if path.has_prefix("/") || path.contains("\\") {
        raise Invalid("unsupported absolute manifest path")
      }
      for component in path.split("/") {
        if component == "." || component == ".." {
          raise Invalid("unsafe manifest path component")
        }
      }
      if !names.contains(path) && !path.has_suffix("/") {
        raise Invalid("manifest references absent package file")
      }
    }
    for nested in entry.elements() {
      if nested.name == read_manifest + "encryption-data" {
        raise Invalid("encrypted ODF package content is unsupported")
      }
      if !nested.name.has_prefix(read_manifest) {
        manifest_extension_event(processor_events, nested.name)
      } else {
        raise Invalid("unsupported manifest file-entry content")
      }
    }
    entries.set(path, mime)
  }
  if entries.get("/") != Some(media_type) {
    raise Invalid("manifest root media type disagrees with mimetype")
  }
  for name in names {
    if name != "mimetype" &&
      !name.has_prefix("META-INF/") &&
      !name.has_suffix("/") &&
      !entries.contains(name) {
      raise Invalid("package file missing from manifest: \{name}")
    }
  }
}

///|
fn manifest_extension_event(
  events : Array[XmlProcessorEvent],
  name : String,
) -> Unit raise OdsError {
  if events.length() >= 32768 {
    raise Invalid("XML processor event budget exceeded")
  }
  events.push({
    document_part: "META-INF/manifest.xml",
    sequence: events.length(),
    location: "content",
    kind: "MANIFEST_EXTENSION_NOT_INTERPRETED",
    name,
    data: "Foreign manifest element omitted by data-only import",
    public_id: None,
    system_id: None,
    notation: None,
  })
}