///|
/// Protocol planning and compliance helpers for repeatable recordings.

///|
/// A protocol step shown to an operator.
pub(all) struct ProtocolStep {
  ordinal : Int
  name : String
  duration_seconds : Double
  instruction : String
  required : Bool
} derive(FromJson, ToJson, Debug, Eq)

///|
/// A complete operator protocol.
pub(all) struct ProtocolPlan {
  name : String
  version : String
  steps : Array[ProtocolStep]
  target_beats : Int
  target_duration_seconds : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Completion state for a protocol step.
pub(all) struct ProtocolStepResult {
  ordinal : Int
  observed_seconds : Double
  completed : Bool
  deviation_seconds : Double
  note : String
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Protocol completion report.
pub(all) struct ProtocolRunReport {
  plan_name : String
  completed_steps : Int
  required_steps : Int
  completion_ratio : Double
  duration_error_seconds : Double
  passed : Bool
  results : Array[ProtocolStepResult]
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Make a resting morning protocol plan.
pub fn morning_protocol_plan() -> ProtocolPlan {
  {
    name: "morning-resting-hrv",
    version: "1.0",
    steps: [
      {
        ordinal: 1,
        name: "settle",
        duration_seconds: 120.0,
        instruction: "sit quietly and breathe normally",
        required: true,
      },
      {
        ordinal: 2,
        name: "record",
        duration_seconds: 300.0,
        instruction: "record a continuous RR stream",
        required: true,
      },
      {
        ordinal: 3,
        name: "review",
        duration_seconds: 30.0,
        instruction: "review signal quality before scoring",
        required: true,
      },
    ],
    target_beats: 300,
    target_duration_seconds: 300.0,
  }
}

///|
/// Make a short field protocol plan.
pub fn field_protocol_plan() -> ProtocolPlan {
  {
    name: "field-60-second-hrv",
    version: "1.0",
    steps: [
      {
        ordinal: 1,
        name: "settle",
        duration_seconds: 30.0,
        instruction: "remain still",
        required: true,
      },
      {
        ordinal: 2,
        name: "record",
        duration_seconds: 60.0,
        instruction: "record a clean RR stream",
        required: true,
      },
    ],
    target_beats: 60,
    target_duration_seconds: 60.0,
  }
}

///|
/// Return the total planned duration.
pub fn protocol_plan_duration(plan : ProtocolPlan) -> Double {
  sum_values(plan.steps.map(fn(step) { step.duration_seconds }))
}

///|
/// Find a step by ordinal.
pub fn protocol_step(plan : ProtocolPlan, ordinal : Int) -> ProtocolStep? {
  for step in plan.steps {
    if step.ordinal == ordinal {
      return Some(step)
    }
  }
  None
}

///|
/// Validate step results against a plan.
pub fn evaluate_protocol_run(
  plan : ProtocolPlan,
  results : Array[ProtocolStepResult],
  tolerance_seconds : Double,
) -> ProtocolRunReport {
  let tolerance = if tolerance_seconds < 0.0 { 0.0 } else { tolerance_seconds }
  let mut completed = 0
  let mut required = 0
  for step in plan.steps {
    if step.required {
      required += 1
    }
  }
  for result in results {
    if result.completed && result.deviation_seconds.abs() <= tolerance {
      completed += 1
    }
  }
  let completion = if required == 0 {
    1.0
  } else {
    completed.to_double() / required.to_double()
  }
  let observed = sum_values(results.map(fn(result) { result.observed_seconds }))
  {
    plan_name: plan.name,
    completed_steps: completed,
    required_steps: required,
    completion_ratio: completion,
    duration_error_seconds: observed - plan.target_duration_seconds,
    passed: completion >= 1.0,
    results,
  }
}

///|
/// Generate missing step results with actionable notes.
pub fn missing_protocol_results(
  plan : ProtocolPlan,
  results : Array[ProtocolStepResult],
) -> Array[ProtocolStepResult] {
  let output = []
  for step in plan.steps {
    let mut found = false
    for result in results {
      if result.ordinal == step.ordinal {
        found = true
      }
    }
    if found {
      for result in results {
        if result.ordinal == step.ordinal {
          output.push(result)
        }
      }
    } else {
      output.push({
        ordinal: step.ordinal,
        observed_seconds: 0.0,
        completed: false,
        deviation_seconds: -step.duration_seconds,
        note: "step was not recorded",
      })
    }
  }
  output
}

///|
/// Return a human-readable next action.
pub fn protocol_next_action(
  plan : ProtocolPlan,
  results : Array[ProtocolStepResult],
) -> String {
  for step in plan.steps {
    let mut completed = false
    for result in results {
      if result.ordinal == step.ordinal && result.completed {
        completed = true
      }
    }
    if !completed {
      return "complete step " +
        step.ordinal.to_string() +
        ": " +
        step.instruction
    }
  }
  "protocol complete; review quality before publishing"
}

///|
/// Return a protocol feature vector.
pub fn protocol_run_feature_vector(report : ProtocolRunReport) -> Array[Double] {
  [
    report.completed_steps.to_double(),
    report.required_steps.to_double(),
    report.completion_ratio,
    report.duration_error_seconds,
    if report.passed {
      1.0
    } else {
      0.0
    },
  ]
}

///|
/// Return whether a plan is internally consistent.
pub fn protocol_plan_is_valid(plan : ProtocolPlan) -> Bool {
  if plan.steps.length() == 0 ||
    plan.target_beats <= 0 ||
    plan.target_duration_seconds <= 0.0 {
    return false
  }
  for i in 0..