///|
/// Generates synthetic Vertical Ground Reaction Force (vGRF) waveform for Countermovement Jump (CMJ).
pub fn generate_synthetic_cmj_trial(
  subject_id : String,
  sampling_rate : Double,
  body_mass_kg : Double,
) -> @core.ForceTrial {
  let meta = @core.TrialMetadata::new(
    subject_id, sampling_rate, body_mass_kg, "both",
  )
  let samples : Array[@core.ForceSample] = []
  let g = @kinetic.gravity_constant()
  let bw_n = body_mass_kg * g
  let dt = 1.0 / sampling_rate

  // 1. Quiet standing (500 ms)
  let n_quiet = (0.500 * sampling_rate).to_int()
  for i = 0; i < n_quiet; i = i + 1 {
    let t = i.to_double() * dt
    let noise = if i % 2 == 0 { 2.5 } else { -2.5 }
    samples.push(
      @core.ForceSample::new(
        t,
        @core.Vec3::new(0.0, 0.0, bw_n + noise),
        @core.Vec3::zero(),
        @core.CoPPoint::new(0.0, 0.0),
      ),
    )
  }

  // 2. Unloading & Dip Phase (200 ms)
  let n_dip = (0.200 * sampling_rate).to_int()
  let start_t1 = n_quiet.to_double() * dt
  let pi = @math.PI
  for i = 0; i < n_dip; i = i + 1 {
    let t = start_t1 + i.to_double() * dt
    let ratio = i.to_double() / n_dip.to_double() * pi
    let fz = bw_n - bw_n * 0.45 * @math.sin(ratio)
    samples.push(
      @core.ForceSample::new(
        t,
        @core.Vec3::new(0.0, 0.0, fz),
        @core.Vec3::zero(),
        @core.CoPPoint::new(0.0, 0.0),
      ),
    )
  }

  // 3. Braking & Propulsive Drive Phase (250 ms)
  let n_prop = (0.250 * sampling_rate).to_int()
  let start_t2 = start_t1 + n_dip.to_double() * dt
  for i = 0; i < n_prop; i = i + 1 {
    let t = start_t2 + i.to_double() * dt
    let ratio = i.to_double() / n_prop.to_double() * pi
    let fz = bw_n + bw_n * 1.5 * @math.sin(ratio)
    samples.push(
      @core.ForceSample::new(
        t,
        @core.Vec3::new(0.0, 0.0, fz),
        @core.Vec3::zero(),
        @core.CoPPoint::new(0.0, 0.0),
      ),
    )
  }

  // 4. Flight Phase (400 ms)
  let n_flight = (0.400 * sampling_rate).to_int()
  let start_t3 = start_t2 + n_prop.to_double() * dt
  for i = 0; i < n_flight; i = i + 1 {
    let t = start_t3 + i.to_double() * dt
    samples.push(
      @core.ForceSample::new(
        t,
        @core.Vec3::zero(),
        @core.Vec3::zero(),
        @core.CoPPoint::zero(),
      ),
    )
  }

  // 5. Landing Impact Phase (350 ms)
  let n_land = (0.350 * sampling_rate).to_int()
  let start_t4 = start_t3 + n_flight.to_double() * dt
  for i = 0; i < n_land; i = i + 1 {
    let t = start_t4 + i.to_double() * dt
    let decay = 1.0 - i.to_double() / n_land.to_double()
    let fz = bw_n + bw_n * 2.2 * decay
    samples.push(
      @core.ForceSample::new(
        t,
        @core.Vec3::new(0.0, 0.0, fz),
        @core.Vec3::zero(),
        @core.CoPPoint::new(0.0, 0.0),
      ),
    )
  }

  @core.ForceTrial::new(meta, samples)
}

///|
/// Generates synthetic quiet standing sway trial (COP trajectory).
pub fn generate_synthetic_sway_trial(
  subject_id : String,
  duration_s : Double,
  sampling_rate : Double,
) -> @core.ForceTrial {
  let meta = @core.TrialMetadata::new(subject_id, sampling_rate, 70.0, "both")
  let samples : Array[@core.ForceSample] = []
  let n = (duration_s * sampling_rate).to_int()
  let dt = 1.0 / sampling_rate
  let bw_n = 70.0 * 9.80665
  let pi = @math.PI

  for i = 0; i < n; i = i + 1 {
    let t = i.to_double() * dt
    let cop_x = 5.0 * @math.sin(t * 0.5 * pi)
    let cop_y = 8.0 * @math.sin(t * 0.3 * pi)
    samples.push(
      @core.ForceSample::new(
        t,
        @core.Vec3::new(0.0, 0.0, bw_n),
        @core.Vec3::zero(),
        @core.CoPPoint::new(cop_x, cop_y),
      ),
    )
  }

  @core.ForceTrial::new(meta, samples)
}

///|
/// Generates synthetic Isometric Mid-Thigh Pull (IMTP) force trial.
pub fn generate_synthetic_imtp_trial(
  subject_id : String,
  sampling_rate : Double,
  body_mass_kg : Double,
) -> @core.ForceTrial {
  let meta = @core.TrialMetadata::new(
    subject_id, sampling_rate, body_mass_kg, "both",
  )
  let samples : Array[@core.ForceSample] = []
  let bw_n = body_mass_kg * 9.80665
  let dt = 1.0 / sampling_rate

  // 1. Quiet standing (300 ms)
  let n_quiet = (0.300 * sampling_rate).to_int()
  for i = 0; i < n_quiet; i = i + 1 {
    let t = i.to_double() * dt
    samples.push(
      @core.ForceSample::new(
        t,
        @core.Vec3::new(0.0, 0.0, bw_n),
        @core.Vec3::zero(),
        @core.CoPPoint::zero(),
      ),
    )
  }

  // 2. Maximal Isometric Pull (1500 ms)
  let n_pull = (1.500 * sampling_rate).to_int()
  let start_t = n_quiet.to_double() * dt
  let peak_n = bw_n * 4.2
  for i = 0; i < n_pull; i = i + 1 {
    let t = start_t + i.to_double() * dt
    let progress = i.to_double() / (0.300 * sampling_rate)
    let ramp = if progress > 1.0 { 1.0 } else { progress }
    let fz = bw_n + (peak_n - bw_n) * ramp
    samples.push(
      @core.ForceSample::new(
        t,
        @core.Vec3::new(0.0, 0.0, fz),
        @core.Vec3::zero(),
        @core.CoPPoint::zero(),
      ),
    )
  }

  @core.ForceTrial::new(meta, samples)
}