///|
pub struct EnsembleResult {
  result : DetectionResult
  votes : Int
  detector_count : Int
  agreement : Double
}

///|
pub struct OnlineEnsemble {
  detectors : Array[PipelineDetector]
  quorum : Int
  mut index : Int
}

///|
pub fn OnlineEnsemble::new(
  detectors : Array[PipelineDetector],
  quorum? : Int = 1,
) -> OnlineEnsemble {
  let safe_quorum = if quorum < 1 {
    1
  } else if quorum > detectors.length() {
    detectors.length()
  } else {
    quorum
  }
  { detectors, quorum: safe_quorum, index: 0 }
}

///|
pub fn OnlineEnsemble::count(self : OnlineEnsemble) -> Int {
  self.detectors.length()
}

///|
pub fn OnlineEnsemble::update(
  self : OnlineEnsemble,
  value : Double,
) -> EnsembleResult {
  self.index += 1
  let mut votes = 0
  let mut best = DetectionResult::quiet(index=self.index)
  for detector in self.detectors {
    let result = detector.update(value, self.index)
    if result.changed {
      votes += 1
    }
    if result.score > best.score {
      best = result
    }
  }
  let agreement = if self.detectors.length() == 0 {
    0.0
  } else {
    votes.to_double() / self.detectors.length().to_double()
  }
  {
    result: {
      changed: votes >= self.quorum,
      score: best.score,
      confidence: agreement * best.confidence,
      direction: best.direction,
      index: self.index,
      evidence: agreement,
    },
    votes,
    detector_count: self.detectors.length(),
    agreement,
  }
}

///|
pub fn weighted_consensus(
  results : Array[DetectionResult],
  weights : Array[Double],
  quorum : Double,
) -> DetectionResult {
  if results.length() == 0 {
    return DetectionResult::quiet()
  }
  let score = combine_scores(results.map(r => r.score), weights)
  let mut changed_weight = 0.0
  let mut best = results[0]
  for i in 0.. best.score {
      best = results[i]
    }
  }
  let total_weight = sum(weights)
  let agreement = if total_weight == 0.0 {
    0.0
  } else {
    changed_weight / total_weight
  }
  {
    changed: agreement >= quorum,
    score,
    confidence: agreement * best.confidence,
    direction: best.direction,
    index: best.index,
    evidence: agreement,
  }
}

///|
pub fn detector_reliability(results : Array[DetectionResult]) -> Double {
  if results.length() == 0 {
    return 0.0
  }
  let mut confident = 0
  for result in results {
    if result.confidence >= 0.5 {
      confident += 1
    }
  }
  confident.to_double() / results.length().to_double()
}