///|
/// Validation result for trial metadata received from an acquisition system.
pub struct MetadataValidation {
  is_valid : Bool
  flags : Array[String]
} derive(Debug)

///|
pub fn sampling_rate_is_supported(
  metadata : TrialMetadata,
  minimum_hz : Double,
  maximum_hz : Double,
) -> Bool {
  let lower = if minimum_hz < maximum_hz { minimum_hz } else { maximum_hz }
  let upper = if minimum_hz < maximum_hz { maximum_hz } else { minimum_hz }
  !metadata.sampling_rate.is_nan() &&
  !metadata.sampling_rate.is_inf() &&
  metadata.sampling_rate >= lower &&
  metadata.sampling_rate <= upper
}

///|
/// Validates identifiers, physical mass, rate, and supported bilateral side.
pub fn validate_trial_metadata(metadata : TrialMetadata) -> MetadataValidation {
  let flags : Array[String] = []
  if metadata.subject_id.length() == 0 {
    flags.push("missing_subject_id")
  }
  if metadata.sampling_rate <= 0.0 ||
    metadata.sampling_rate.is_nan() ||
    metadata.sampling_rate.is_inf() {
    flags.push("invalid_sampling_rate")
  }
  if metadata.nominal_body_mass <= 0.0 ||
    metadata.nominal_body_mass.is_nan() ||
    metadata.nominal_body_mass.is_inf() {
    flags.push("invalid_body_mass")
  }
  if metadata.side != "left" &&
    metadata.side != "right" &&
    metadata.side != "both" {
    flags.push("unsupported_side")
  }
  { is_valid: flags.length() == 0, flags }
}

///|
/// Stable one-line metadata representation for logs and benchmark provenance.
pub fn format_metadata_summary(metadata : TrialMetadata) -> String {
  "subject_id=" +
  metadata.subject_id +
  " sampling_rate_hz=" +
  metadata.sampling_rate.to_string() +
  " body_mass_kg=" +
  metadata.nominal_body_mass.to_string() +
  " side=" +
  metadata.side
}

///|
/// Returns the nominal sample period in seconds, or zero for invalid rates.
pub fn metadata_sampling_period_s(metadata : TrialMetadata) -> Double {
  if metadata.sampling_rate <= 0.0 ||
    metadata.sampling_rate.is_nan() ||
    metadata.sampling_rate.is_inf() {
    0.0
  } else {
    1.0 / metadata.sampling_rate
  }
}

///|
/// Compact CSV row for provenance logs.
pub fn format_metadata_csv(metadata : TrialMetadata) -> String {
  metadata.subject_id +
  "," +
  metadata.sampling_rate.to_string() +
  "," +
  metadata.nominal_body_mass.to_string() +
  "," +
  metadata.side +
  "\n"
}

///|
/// JSON-compatible metadata object for machine-readable provenance.
pub fn format_metadata_json(metadata : TrialMetadata) -> String {
  let builder = StringBuilder()
  builder.write_string("{\n")
  builder.write_string("  \"subject_id\": \"")
  builder.write_string(metadata.subject_id)
  builder.write_string("\",\n")
  builder.write_string("  \"sampling_rate_hz\": ")
  builder.write_string(metadata.sampling_rate.to_string())
  builder.write_string(",\n")
  builder.write_string("  \"nominal_body_mass_kg\": ")
  builder.write_string(metadata.nominal_body_mass.to_string())
  builder.write_string(",\n")
  builder.write_string("  \"side\": \"")
  builder.write_string(metadata.side)
  builder.write_string("\"\n")
  builder.write_string("}")
  builder.to_string()
}

///|
/// Joins validation flags into a stable diagnostic string.
pub fn metadata_flag_summary(validation : MetadataValidation) -> String {
  if validation.is_valid {
    return "valid"
  }
  let builder = StringBuilder()
  for i = 0; i < validation.flags.length(); i = i + 1 {
    if i > 0 {
      builder.write_string("|")
    }
    builder.write_string(validation.flags[i])
  }
  builder.to_string()
}

///|
pub fn metadata_is_bilateral(metadata : TrialMetadata) -> Bool {
  metadata.side == "both"
}

///|
pub fn metadata_side_code(metadata : TrialMetadata) -> Int {
  if metadata.side == "left" {
    -1
  } else if metadata.side == "right" {
    1
  } else {
    0
  }
}

///|
pub fn metadata_side_name(metadata : TrialMetadata) -> String {
  if metadata.side == "left" {
    "left"
  } else if metadata.side == "right" {
    "right"
  } else if metadata.side == "both" {
    "both"
  } else {
    "unknown"
  }
}

///|
pub fn metadata_is_unilateral(metadata : TrialMetadata) -> Bool {
  metadata.side == "left" || metadata.side == "right"
}

///|
pub fn metadata_has_subject_id(metadata : TrialMetadata) -> Bool {
  metadata.subject_id.length() > 0
}

///|
pub fn metadata_is_complete(metadata : TrialMetadata) -> Bool {
  validate_trial_metadata(metadata).is_valid
}

///|
pub fn metadata_is_physical(metadata : TrialMetadata) -> Bool {
  metadata.nominal_body_mass > 0.0 && !metadata.nominal_body_mass.is_nan()
}