///|
/// End-to-end stream pipeline: preprocess packets, fuse a state estimate, and
/// maintain lifecycle/health metadata for production telemetry.
pub struct PipelineEvent {
  fusion : FusionEvent
  lifecycle : TrackLifecycle
  sensor_score : Double
  rolling_mean : Double
  rolling_variance : Double
} derive(Debug)

///|
pub fn PipelineEvent::fusion(self : PipelineEvent) -> FusionEvent {
  self.fusion
}

///|
pub fn PipelineEvent::lifecycle(self : PipelineEvent) -> TrackLifecycle {
  self.lifecycle
}

///|
pub fn PipelineEvent::sensor_score(self : PipelineEvent) -> Double {
  self.sensor_score
}

///|
pub fn PipelineEvent::rolling_mean(self : PipelineEvent) -> Double {
  self.rolling_mean
}

///|
pub fn PipelineEvent::rolling_variance(self : PipelineEvent) -> Double {
  self.rolling_variance
}

///|
pub struct SensorPipeline {
  fusion : SensorFusion
  tracker : TrackManager
  health : Map[String, SensorHealth]
  windows : Map[String, RollingWindow]
  window_capacity : Int
}

///|
pub fn SensorPipeline::new(
  fusion : SensorFusion,
  confirmation_hits : Int,
  deletion_misses : Int,
  window_capacity : Int,
) -> SensorPipeline {
  {
    fusion,
    tracker: TrackManager::new(confirmation_hits, deletion_misses),
    health: {},
    windows: {},
    window_capacity: if window_capacity < 1 {
      1
    } else {
      window_capacity
    },
  }
}

///|
fn SensorPipeline::window_for(
  self : SensorPipeline,
  sensor : String,
) -> RollingWindow {
  match self.windows.get(sensor) {
    Some(window) => window
    None => {
      let window = RollingWindow::new(self.window_capacity)
      self.windows[sensor] = window
      window
    }
  }
}

///|
fn SensorPipeline::health_for(
  self : SensorPipeline,
  sensor : String,
) -> SensorHealth {
  match self.health.get(sensor) {
    Some(value) => value
    None => {
      let value = SensorHealth::new(sensor, 0.2, 0.1)
      self.health[sensor] = value
      value
    }
  }
}

///|
pub fn SensorPipeline::process(
  self : SensorPipeline,
  packet : ObservationPacket,
) -> PipelineEvent {
  let sensor = packet.sensor()
  let window = self.window_for(sensor)
  let value = packet.values()
  if value.length() > 0 {
    window.push(value[0])
  }
  let health = self.health_for(sensor)
  let fusion = self.fusion.process(packet)
  health.observe(fusion.result())
  let lifecycle = self.tracker.observe(fusion.result())
  {
    fusion,
    lifecycle,
    sensor_score: health.score(),
    rolling_mean: window.mean(),
    rolling_variance: window.variance(),
  }
}

///|
pub fn SensorPipeline::process_missing(
  self : SensorPipeline,
  timestamp : Int,
  sensor : String,
) -> PipelineEvent {
  let window = self.window_for(sensor)
  let health = self.health_for(sensor)
  let fusion = self.fusion.process_missing(timestamp, sensor)
  health.observe(fusion.result())
  let lifecycle = self.tracker.observe(fusion.result())
  {
    fusion,
    lifecycle,
    sensor_score: health.score(),
    rolling_mean: window.mean(),
    rolling_variance: window.variance(),
  }
}

///|
pub fn SensorPipeline::run(
  self : SensorPipeline,
  packets : Array[ObservationPacket],
) -> Array[PipelineEvent] {
  let events : Array[PipelineEvent] = []
  for packet in packets {
    events.push(self.process(packet))
  }
  events
}

///|
pub fn SensorPipeline::lifecycle(self : SensorPipeline) -> TrackLifecycle {
  self.tracker.lifecycle()
}

///|
pub fn SensorPipeline::sensor_health(
  self : SensorPipeline,
  sensor : String,
) -> SensorHealth {
  self.health_for(sensor)
}

///|
pub fn SensorPipeline::window(
  self : SensorPipeline,
  sensor : String,
) -> RollingWindow {
  self.window_for(sensor)
}

///|
pub struct PipelineReport {
  events : Int
  accepted : Int
  rejected : Int
  missing : Int
  final_lifecycle : TrackLifecycle
  final_sensor_score : Double
} derive(Debug)

///|
pub fn PipelineReport::events(self : PipelineReport) -> Int {
  self.events
}

///|
pub fn PipelineReport::accepted(self : PipelineReport) -> Int {
  self.accepted
}

///|
pub fn PipelineReport::rejected(self : PipelineReport) -> Int {
  self.rejected
}

///|
pub fn PipelineReport::missing(self : PipelineReport) -> Int {
  self.missing
}

///|
pub fn PipelineReport::final_lifecycle(self : PipelineReport) -> TrackLifecycle {
  self.final_lifecycle
}

///|
pub fn PipelineReport::final_sensor_score(self : PipelineReport) -> Double {
  self.final_sensor_score
}

///|
pub fn summarize_pipeline(
  events : Array[PipelineEvent],
  sensor : String,
) -> PipelineReport {
  let mut accepted = 0
  let mut rejected = 0
  let mut missing = 0
  let mut lifecycle = Tentative
  let mut score = 1.0
  for event in events {
    lifecycle = event.lifecycle()
    score = event.sensor_score()
    match event.fusion().result() {
      Accepted => accepted = accepted + 1
      MissingMeasurement => missing = missing + 1
      RejectedByGate | InvalidMeasurement | SingularInnovation =>
        rejected = rejected + 1
    }
  }
  let _ = sensor
  {
    events: accepted + rejected + missing,
    accepted,
    rejected,
    missing,
    final_lifecycle: lifecycle,
    final_sensor_score: score,
  }
}