///|
/// Per-window feature record for batch triage.
pub struct WindowFeatureRow {
  start_time : Double
  end_time : Double
  rms : Double
  peak : Double
  dominant_frequency : Double
  spectral_entropy : Double
  quality_score : Double
  event_count : Int
}

///|
/// Summary of a batch analysis.
pub struct BatchAnalysis {
  rows : Array[WindowFeatureRow]
  total_windows : Int
  accepted_windows : Int
  mean_quality : Double
  strongest_peak : Double
}

///|
/// Analyze each trace window using the same deterministic feature pipeline.
pub fn analyze_trace_windows(
  trace : Trace,
  window_size : Int,
  step : Int,
  minimum_quality : Double,
) -> BatchAnalysis {
  let rows = Array::new()
  let windows = trace_windows(trace, window_size, step, false)
  let mut quality_sum = 0.0
  let mut accepted = 0
  let mut strongest = 0.0
  for window in windows {
    let values = window.data
    let report = assess_quality(
      Trace::new(
        trace.station,
        trace.channel,
        trace.sampling_rate,
        window.start_time,
        values,
      ),
      1.0e6,
      0.0000001,
    )
    let spectrum = hann_periodogram(window.data, trace.sampling_rate)
    let features = extract_spectral_features(spectrum)
    let detector = default_event_config(trace.delta(), window.start_time)
    let events = detect_events(window.data, detector)
    let peak = maximum_sample(window.data)
      .abs()
      .max(minimum_sample(window.data).abs())
    rows.push({
      start_time: window.start_time,
      end_time: window.end_time,
      rms: rms_samples(window.data),
      peak,
      dominant_frequency: features.dominant_frequency,
      spectral_entropy: features.entropy,
      quality_score: report.score,
      event_count: events.length(),
    })
    quality_sum += report.score
    if report.score >= minimum_quality {
      accepted += 1
    }
    if peak > strongest {
      strongest = peak
    }
  }
  let total = rows.length()
  {
    rows,
    total_windows: total,
    accepted_windows: accepted,
    mean_quality: if total == 0 {
      0.0
    } else {
      quality_sum / total.to_double()
    },
    strongest_peak: strongest,
  }
}

///|
/// Return rows containing at least one detected event.
pub fn event_windows(batch : BatchAnalysis) -> Array[WindowFeatureRow] {
  batch.rows.filter(row => row.event_count > 0)
}

///|
/// Return rows whose RMS exceeds a physical amplitude threshold.
pub fn energetic_windows(
  batch : BatchAnalysis,
  threshold : Double,
) -> Array[WindowFeatureRow] {
  batch.rows.filter(row => row.rms >= threshold)
}

///|
/// Return a quality-weighted peak score for ranking windows.
pub fn window_priority(row : WindowFeatureRow) -> Double {
  row.peak * row.quality_score * (1.0 + row.event_count.to_double())
}

///|
/// Return the highest-priority row, if any.
pub fn strongest_window(batch : BatchAnalysis) -> WindowFeatureRow? {
  if batch.rows.is_empty() {
    return None
  }
  let mut best = batch.rows[0]
  for index = 1; index < batch.rows.length(); index = index + 1 {
    if window_priority(batch.rows[index]) > window_priority(best) {
      best = batch.rows[index]
    }
  }
  Some(best)
}

///|
/// Aggregate several traces into one stack and analyze the stack.
pub fn analyze_trace_collection(
  traces : Array[Trace],
  window_size : Int,
  step : Int,
) -> Result[BatchAnalysis, String] {
  match stack_traces(traces) {
    Ok(stacked) => Ok(analyze_trace_windows(stacked, window_size, step, 0.5))
    Err(message) => Err(message)
  }
}

///|
/// Return a stable textual table for human inspection.
pub fn batch_analysis_text(batch : BatchAnalysis) -> String {
  let mut output = "start,end,rms,peak,dominant_hz,entropy,quality,events\n"
  for row in batch.rows {
    output = output +
      row.start_time.to_string() +
      "," +
      row.end_time.to_string() +
      "," +
      row.rms.to_string() +
      "," +
      row.peak.to_string() +
      "," +
      row.dominant_frequency.to_string() +
      "," +
      row.spectral_entropy.to_string() +
      "," +
      row.quality_score.to_string() +
      "," +
      row.event_count.to_string() +
      "\n"
  }
  output
}