///|
/// Recording protocol used to validate an analysis request.
pub(all) struct RecordingProtocol {
  minimum_beats : Int
  target_duration_seconds : Double
  minimum_duration_seconds : Double
  maximum_artifact_ratio : Double
  required_sample_rate_hz : Double
  require_ordered_dates : Bool
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Protocol validation result with actionable reasons.
pub(all) struct ProtocolValidation {
  passed : Bool
  reasons : Array[String]
  sample_count : Int
  duration_seconds : Double
  artifact_ratio : Double
  date_gaps : Int
  quality_grade : String
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Standard resting protocol: five minutes, at least 300 intervals.
pub fn RecordingProtocol::resting() -> RecordingProtocol {
  {
    minimum_beats: 300,
    target_duration_seconds: 300.0,
    minimum_duration_seconds: 120.0,
    maximum_artifact_ratio: 0.15,
    required_sample_rate_hz: 4.0,
    require_ordered_dates: false,
  }
}

///|
/// Short field protocol suitable for a morning readiness check.
pub fn RecordingProtocol::morning() -> RecordingProtocol {
  {
    minimum_beats: 60,
    target_duration_seconds: 60.0,
    minimum_duration_seconds: 30.0,
    maximum_artifact_ratio: 0.20,
    required_sample_rate_hz: 4.0,
    require_ordered_dates: true,
  }
}

///|
/// Validate a recording against a protocol.
pub fn validate_recording_protocol(
  intervals : Array[Double],
  config : HrvConfig,
  protocol : RecordingProtocol,
  dates : Array[String],
) -> ProtocolValidation {
  let validation = validate_intervals(intervals, config)
  let duration = sum_values(valid_intervals(intervals, config)) / 1000.0
  let artifact_ratio = if intervals.length() == 0 {
    1.0
  } else {
    validation.invalid.to_double() / intervals.length().to_double()
  }
  let gaps = missing_calendar_days(dates)
  let reasons = []
  if intervals.length() < protocol.minimum_beats {
    reasons.push("recording has too few intervals")
  }
  if duration < protocol.minimum_duration_seconds {
    reasons.push("recording is shorter than the protocol minimum")
  }
  if artifact_ratio > protocol.maximum_artifact_ratio {
    reasons.push("artifact ratio exceeds protocol limit")
  }
  if protocol.require_ordered_dates && !dates_are_ordered(dates) {
    reasons.push("dates must be valid and strictly ordered")
  }
  {
    passed: reasons.length() == 0,
    reasons,
    sample_count: intervals.length(),
    duration_seconds: duration,
    artifact_ratio,
    date_gaps: gaps,
    quality_grade: quality_grade(validation),
  }
}

///|
/// Return a protocol-specific expected recording duration.
pub fn protocol_duration_error(
  duration_seconds : Double,
  protocol : RecordingProtocol,
) -> Double {
  absolute_difference(duration_seconds, protocol.target_duration_seconds)
}

///|
/// Return a useful protocol recommendation.
pub fn protocol_recommendation(validation : ProtocolValidation) -> String {
  if validation.passed {
    "recording meets the selected protocol"
  } else if validation.artifact_ratio > 0.30 {
    "repeat the recording with improved sensor contact"
  } else if validation.duration_seconds < 30.0 {
    "record for at least the protocol minimum duration"
  } else {
    "review the protocol reasons before using the score"
  }
}