///|
/// Operational profile around a calibrated device record.
pub(all) struct DeviceProfile {
  device : Device
  calibration_date : String
  calibration_expiry : String
  owner : String
}

///|
/// Create a device profile with ISO-8601 date strings.
pub fn device_profile(
  device~ : Device,
  calibration_date~ : String,
  calibration_expiry~ : String,
  owner~ : String,
) -> DeviceProfile {
  { device, calibration_date, calibration_expiry, owner }
}

///|
fn date_not_after(left : String, right : String) -> Bool {
  left == right || left < right
}

///|
/// Return whether calibration covers a requested date.
pub fn calibration_is_current(profile : DeviceProfile, date : String) -> Bool {
  date_not_after(profile.calibration_date, date) &&
  date_not_after(date, profile.calibration_expiry)
}

///|
/// Return a stable calibration status label.
pub fn device_profile_status(profile : DeviceProfile, date : String) -> String {
  if profile.calibration_date.trim().length() == 0 ||
    profile.calibration_expiry.trim().length() == 0 {
    "unknown"
  } else if calibration_is_current(profile, date) {
    "calibrated"
  } else {
    "expired"
  }
}

///|
/// Return whether the profiled device can measure a feature.
pub fn device_can_measure_with_profile(
  profile : DeviceProfile,
  feature : Feature,
) -> Bool {
  profile.device.can_measure(feature)
}

///|
/// Count catalog features available to a profile.
pub fn profile_capacity(profile : DeviceProfile, catalog : Catalog) -> Int {
  supported_feature_count(profile.device, catalog.features)
}

///|
/// Return whether a profile covers every catalog feature.
pub fn device_can_cover_catalog(
  profile : DeviceProfile,
  catalog : Catalog,
) -> Bool {
  profile_capacity(profile, catalog) == catalog.features.length()
}

///|
/// Return the number of supported feature kinds.
pub fn profile_supported_kind_count(profile : DeviceProfile) -> Int {
  profile.device.supported_kinds.length()
}

///|
/// Return the reach margin for a feature.
pub fn profile_reach_margin(
  profile : DeviceProfile,
  feature : Feature,
) -> Double {
  profile.device.reach - feature.nominal.abs()
}

///|
/// Return an accuracy rank where lower is better.
pub fn profile_accuracy_rank(profile : DeviceProfile) -> Double {
  profile.device.accuracy
}

///|
/// Return whether the profile has positive physical capabilities.
pub fn profile_is_usable(profile : DeviceProfile) -> Bool {
  profile.device.accuracy > 0.0 &&
  profile.device.reach > 0.0 &&
  profile.device.supported_kinds.length() > 0
}

///|
/// Return whether the profile needs recalibration before a date.
pub fn profile_needs_recalibration(
  profile : DeviceProfile,
  date : String,
) -> Bool {
  !calibration_is_current(profile, date)
}

///|
/// Return the list of unsupported catalog feature IDs.
pub fn profile_unsupported_ids(
  profile : DeviceProfile,
  catalog : Catalog,
) -> Array[String] {
  let result = []
  for feature in catalog.features {
    if !profile.device.can_measure(feature) {
      result.push(feature.id)
    }
  }
  result
}

///|
/// Render a stable device profile summary.
pub fn profile_summary(profile : DeviceProfile, date : String) -> String {
  "device=" +
  profile.device.id +
  " owner=" +
  profile.owner +
  " status=" +
  device_profile_status(profile, date) +
  " accuracy=" +
  profile.device.accuracy.to_string() +
  " reach=" +
  profile.device.reach.to_string() +
  " kinds=" +
  profile.device.supported_kinds.length().to_string()
}

///|
/// Return whether the profile is both usable and currently calibrated.
pub fn profile_is_ready(profile : DeviceProfile, date : String) -> Bool {
  profile_is_usable(profile) && calibration_is_current(profile, date)
}

///|
/// Return a warning string for a profile/date pair.
pub fn profile_warning(profile : DeviceProfile, date : String) -> String {
  if !profile_is_usable(profile) {
    "device capabilities are invalid"
  } else if !calibration_is_current(profile, date) {
    "device calibration is expired"
  } else {
    ""
  }
}