///|
fn parameter_record(
  name : String,
  value : Double,
  unit : String,
  lower_bound : Double,
  upper_bound : Double,
  source : String,
) -> ParameterRecord {
  ParameterRecord::new(
    name~,
    value~,
    unit~,
    lower_bound~,
    upper_bound~,
    source~,
  )
}

///|
fn antoine_records(component : Component) -> Array[ParameterRecord] {
  [
    parameter_record(
      component.name + ".antoine.a",
      component.antoine.a,
      "log10(mmHg)",
      -100.0,
      100.0,
      component.antoine.note,
    ),
    parameter_record(
      component.name + ".antoine.b",
      component.antoine.b,
      "K",
      0.0,
      10000.0,
      component.antoine.note,
    ),
    parameter_record(
      component.name + ".antoine.c",
      component.antoine.c,
      "C",
      -500.0,
      500.0,
      component.antoine.note,
    ),
    parameter_record(
      component.name + ".antoine.t_min_k",
      component.antoine.t_min_k,
      "K",
      0.0,
      2000.0,
      component.antoine.note,
    ),
    parameter_record(
      component.name + ".antoine.t_max_k",
      component.antoine.t_max_k,
      "K",
      0.0,
      2000.0,
      component.antoine.note,
    ),
  ]
}

///|
pub fn public_parameter_catalog() -> Array[ParameterRecord] {
  let records : Array[ParameterRecord] = []
  for component in pressure_catalog_components() {
    for record in antoine_records(component) {
      records.push(record)
    }
  }
  records
}

///|
pub fn validate_parameter_records(
  records : Array[ParameterRecord],
) -> ParameterValidationReport {
  let messages : Array[String] = []
  let issue_count = for i = 0, count = 0; i < records.length(); {
    let record = records[i]
    let invalid_bounds = record.lower_bound >= record.upper_bound
    let outside = record.value < record.lower_bound ||
      record.value > record.upper_bound
    let invalid_unit = record.unit.length() == 0
    let invalid_source = record.source.length() == 0
    let issues = (if invalid_bounds { 1 } else { 0 }) +
      (if outside { 1 } else { 0 }) +
      (if invalid_unit { 1 } else { 0 }) +
      (if invalid_source { 1 } else { 0 })
    if invalid_bounds {
      messages.push(record.name + ": invalid bounds")
    }
    if outside {
      messages.push(record.name + ": value outside bounds")
    }
    if invalid_unit {
      messages.push(record.name + ": missing unit")
    }
    if invalid_source {
      messages.push(record.name + ": missing source")
    }
    continue i + 1, count + issues
  } nobreak {
    count
  }
  ParameterValidationReport::{
    valid: issue_count == 0,
    issue_count,
    checked_count: records.length(),
    messages,
  }
}

///|
pub fn parameter_catalog_is_valid(records : Array[ParameterRecord]) -> Bool {
  validate_parameter_records(records).valid
}

///|
pub fn parameter_records_for_component(
  component : Component,
) -> Array[ParameterRecord] {
  antoine_records(component)
}

///|
pub fn parameter_record_value(
  records : Array[ParameterRecord],
  name : String,
) -> Double raise VleError {
  for record in records {
    if record.name == name {
      return record.value
    }
  }
  raise VleError::InvalidParameter("parameter name was not found")
}

///|
pub fn parameter_records_in_unit(
  records : Array[ParameterRecord],
  unit : String,
) -> Array[ParameterRecord] {
  [
    for record in records if record.unit == unit => record
  ]
}

///|
pub fn parameter_records_with_source(
  records : Array[ParameterRecord],
  source_fragment : String,
) -> Array[ParameterRecord] {
  [
    for record in records if record.source.contains(source_fragment) => record
  ]
}

///|
pub fn parameter_catalog_units(
  records : Array[ParameterRecord],
) -> Array[String] {
  let result : Array[String] = []
  for record in records {
    if !result.contains(record.unit) {
      result.push(record.unit)
    }
  }
  result
}

///|
pub fn parameter_catalog_sources(
  records : Array[ParameterRecord],
) -> Array[String] {
  let result : Array[String] = []
  for record in records {
    if !result.contains(record.source) {
      result.push(record.source)
    }
  }
  result
}

///|
pub fn parameter_range_width(record : ParameterRecord) -> Double {
  record.upper_bound - record.lower_bound
}

///|
pub fn parameter_relative_position(
  record : ParameterRecord,
) -> Double raise VleError {
  if record.upper_bound <= record.lower_bound {
    raise VleError::InvalidRange(
      low=record.lower_bound,
      high=record.upper_bound,
    )
  }
  (record.value - record.lower_bound) / parameter_range_width(record)
}

///|
pub fn parameter_is_near_lower_bound(
  record : ParameterRecord,
  fraction : Double,
) -> Bool raise VleError {
  if fraction < 0.0 || fraction > 1.0 {
    raise VleError::InvalidParameter(
      "bound fraction must be between zero and one",
    )
  }
  parameter_relative_position(record) <= fraction
}

///|
pub fn parameter_is_near_upper_bound(
  record : ParameterRecord,
  fraction : Double,
) -> Bool raise VleError {
  if fraction < 0.0 || fraction > 1.0 {
    raise VleError::InvalidParameter(
      "bound fraction must be between zero and one",
    )
  }
  parameter_relative_position(record) >= 1.0 - fraction
}