///|
fn manifest_node(
  index : ModelIndex,
  product : Product,
) -> ManifestNode raise ParseError {
  let property_sets = property_sets_for_product(index, product)
  let geometries = geometries_for_product(index, product)
  let bounds = geometries.fold(init=BoundingBox::empty(), (result, geometry) => {
    merge_bounds(result, bounds_of_geometry(geometry))
  })
  {
    product_id: product.id,
    ifc_type: product.ifc_type,
    name: product.name,
    kind: product.kind,
    property_names: property_sets.fold(init=Array::new(), (result, property_set) => {
      for property in property_set.properties {
        result.push(property.name)
      }
      result
    }),
    geometry_count: geometries.length(),
    bounds,
  }
}

///|
pub fn render_manifest(model : Model) -> RenderManifest raise ParseError {
  let index = model.index()
  let nodes = model
    .products()
    .fold(init=Array::new(), (result, product) => {
      result.push(manifest_node(index, product))
      result
    })
  let bounds = nodes.fold(init=BoundingBox::empty(), (result, node) => {
    merge_bounds(result, node.bounds)
  })
  { schema: model.schema(), nodes, bounds }
}

///|
pub fn RenderManifest::find(
  self : RenderManifest,
  product_id : Int,
) -> ManifestNode? {
  for node in self.nodes {
    if node.product_id == product_id {
      return Some(node)
    }
  }
  None
}

///|
pub fn RenderManifest::to_json(self : RenderManifest) -> String {
  let nodes = self.nodes.map(node => {
    "{\"product_id\":" +
    node.product_id.to_string() +
    ",\"type\":" +
    json_string(node.ifc_type) +
    ",\"name\":" +
    optional_string_json(node.name) +
    ",\"properties\":[" +
    node.property_names.map(json_string).join(",") +
    "]" +
    ",\"geometry_count\":" +
    node.geometry_count.to_string() +
    ",\"bounds\":{\"min\":" +
    point_json(node.bounds.min) +
    ",\"max\":" +
    point_json(node.bounds.max) +
    "}}"
  })
  "{\"schema\":" +
  json_string(self.schema) +
  ",\"bounds\":{\"min\":" +
  point_json(self.bounds.min) +
  ",\"max\":" +
  point_json(self.bounds.max) +
  "}" +
  ",\"nodes\":[" +
  join_json(nodes) +
  "]}"
}

///|
pub fn manifest_product_names(manifest : RenderManifest) -> Array[String] {
  manifest.nodes.fold(init=Array::new(), (result, node) => {
    match node.name {
      Some(name) => result.push(name)
      None => ()
    }
    result
  })
}