///|
fn property_set_name(entity : Entity) -> String? {
  optional_string_argument(entity, 2)
}

///|
fn make_property(entity : Entity) -> Property {
  {
    name: optional_string_argument(entity, 0).unwrap_or(""),
    description: optional_string_argument(entity, 1),
    value: entity_value(entity, 2).unwrap_or(Null),
    unit: optional_reference_argument(entity, 3),
  }
}

///|
/// Decode one `IfcPropertySet` and its `IfcPropertySingleValue` members.
pub fn extract_property_set(
  index : ModelIndex,
  id : Int,
) -> PropertySet raise ParseError {
  let entity = index.require(id)
  if entity.name.to_upper() != "IFCPROPERTYSET" {
    raise ParseError::InvalidEntity(
      position=SourcePosition::{ offset: 0, line: 0, column: 0 },
      text="expected IfcPropertySet at #" + id.to_string(),
    )
  }
  let properties : Array[Property] = match entity_value(entity, 4) {
    Some(value) =>
      references(value).fold(init=Array::new(), (result, property_id) => {
        match index.entity(property_id) {
          Some(property) if property.name.to_upper() == "IFCPROPERTYSINGLEVALUE" =>
            result.push(make_property(property))
          _ => ()
        }
        result
      })
    None => []
  }
  {
    id,
    name: property_set_name(entity),
    description: optional_string_argument(entity, 3),
    properties,
  }
}

///|
pub fn property_sets_for_product(
  index : ModelIndex,
  product : Product,
) -> Array[PropertySet] raise ParseError {
  product.property_sets.fold(init=Array::new(), (result, id) => {
    result.push(extract_property_set(index, id))
    result
  })
}

///|
pub fn property_named(
  property_sets : Array[PropertySet],
  name : String,
) -> Property? {
  let wanted = name.to_lower()
  for property_set in property_sets {
    for property in property_set.properties {
      if property.name.to_lower() == wanted {
        return Some(property)
      }
    }
  }
  None
}

///|
pub fn property_string(
  property_sets : Array[PropertySet],
  name : String,
) -> String? {
  match property_named(property_sets, name) {
    Some(property) => property.value.as_string()
    None => None
  }
}

///|
pub fn property_number(
  property_sets : Array[PropertySet],
  name : String,
) -> Double? {
  match property_named(property_sets, name) {
    Some(property) => property.value.as_double()
    None => None
  }
}

///|
pub fn property_names(property_set : PropertySet) -> Array[String] {
  property_set.properties.map(property => property.name)
}

///|
pub fn all_property_sets(
  index : ModelIndex,
) -> Array[PropertySet] raise ParseError {
  index
  .ids_named("IFCPROPERTYSET")
  .fold(init=Array::new(), (result, id) => {
    result.push(extract_property_set(index, id))
    result
  })
}