///|
/// One quality observation aggregated at batch level.
pub(all) struct TrendPoint {
  batch_id : String
  sample_count : Int
  defect_count : Int
}

///|
/// Aggregate quality trend over an ordered batch sequence.
pub(all) struct QualityTrendSummary {
  batch_count : Int
  total_samples : Int
  total_defects : Int
  defect_rate : Double
  minimum_rate : Double
  maximum_rate : Double
  average_rate : Double
}

///|
/// Construct a bounded batch-level trend point.
pub fn trend_point(
  batch_id : String,
  sample_count : Int,
  defect_count : Int,
) -> TrendPoint {
  {
    batch_id,
    sample_count: if sample_count > 0 {
      sample_count
    } else {
      0
    },
    defect_count: if defect_count > 0 {
      defect_count
    } else {
      0
    },
  }
}

///|
fn trend_rate(point : TrendPoint) -> Double {
  if point.sample_count <= 0 {
    0.0
  } else {
    let defects = if point.defect_count > point.sample_count {
      point.sample_count
    } else {
      point.defect_count
    }
    defects.to_double() / point.sample_count.to_double()
  }
}

///|
/// Summarize samples, defects, and per-batch rates.
pub fn summarize_quality_trend(
  points : Array[TrendPoint],
) -> QualityTrendSummary {
  let mut total_samples = 0
  let mut total_defects = 0
  let mut total_rate = 0.0
  let mut minimum_rate = 0.0
  let mut maximum_rate = 0.0
  let mut first = true
  for point in points {
    total_samples += point.sample_count
    total_defects += if point.defect_count > point.sample_count {
      point.sample_count
    } else {
      point.defect_count
    }
    let rate = trend_rate(point)
    total_rate += rate
    if first {
      minimum_rate = rate
      maximum_rate = rate
      first = false
    } else {
      if rate < minimum_rate {
        minimum_rate = rate
      }
      if rate > maximum_rate {
        maximum_rate = rate
      }
    }
  }
  let count = points.length()
  {
    batch_count: count,
    total_samples,
    total_defects,
    defect_rate: if total_samples == 0 {
      0.0
    } else {
      total_defects.to_double() / total_samples.to_double()
    },
    minimum_rate,
    maximum_rate,
    average_rate: if count == 0 {
      0.0
    } else {
      total_rate / count.to_double()
    },
  }
}

///|
/// Classify the direction of the ordered defect-rate sequence.
pub fn trend_direction(points : Array[TrendPoint]) -> String {
  if points.length() < 2 {
    "stable"
  } else {
    let first = trend_rate(points[0])
    let last = trend_rate(points[points.length() - 1])
    if last - first > 0.001 {
      "rising"
    } else if first - last > 0.001 {
      "falling"
    } else {
      "stable"
    }
  }
}

///|
/// Calculate a trailing defect rate for every point.
pub fn rolling_defect_rates(
  points : Array[TrendPoint],
  window : Int,
) -> Array[Double] {
  let result = []
  if window <= 0 {
    result
  } else {
    for index in 0.. window { index + 1 - window } else { 0 }
      let mut samples = 0
      let mut defects = 0
      for cursor in start..<=index {
        samples += points[cursor].sample_count
        defects += if points[cursor].defect_count > points[cursor].sample_count {
          points[cursor].sample_count
        } else {
          points[cursor].defect_count
        }
      }
      result.push(
        if samples == 0 {
          0.0
        } else {
          defects.to_double() / samples.to_double()
        },
      )
    }
    result
  }
}

///|
/// Return the most recent point, when one exists.
pub fn latest_trend_point(points : Array[TrendPoint]) -> TrendPoint? {
  points.get(points.length() - 1)
}

///|
/// Return whether the aggregate rate is below a configured limit.
pub fn trend_is_within_limit(
  points : Array[TrendPoint],
  limit : Double,
) -> Bool {
  limit >= 0.0 && summarize_quality_trend(points).defect_rate <= limit
}

///|
/// Return the number of points with a non-zero defect rate.
pub fn defect_batch_count(points : Array[TrendPoint]) -> Int {
  let mut result = 0
  for point in points {
    if trend_rate(point) > 0.0 {
      result += 1
    }
  }
  result
}

///|
/// Return a compact trend label for reports.
pub fn render_trend_summary(summary : QualityTrendSummary) -> String {
  "batches=" +
  summary.batch_count.to_string() +
  " samples=" +
  summary.total_samples.to_string() +
  " defects=" +
  summary.total_defects.to_string() +
  " rate=" +
  summary.defect_rate.to_string()
}