///|
/// Coverage and coherence measures for a station network event.
pub struct NetworkEventMetrics {
  station_count : Int
  unique_station_count : Int
  mean_distance_km : Double
  maximum_distance_km : Double
  origin_spread_seconds : Double
  amplitude_spread : Double
  magnitude_consensus : Double
  network_score : Double
}

///|
/// A station amplitude observation attached to an event hypothesis.
pub struct StationAmplitudeObservation {
  station : String
  distance_km : Double
  amplitude : Double
  local_magnitude : Double
  confidence : Double
}

///|
/// Construct a station amplitude observation.
pub fn StationAmplitudeObservation::new(
  station : String,
  distance_km : Double,
  amplitude : Double,
  local_magnitude : Double,
  confidence : Double,
) -> StationAmplitudeObservation {
  { station, distance_km, amplitude, local_magnitude, confidence }
}

///|
/// Compute an amplitude-weighted magnitude consensus.
pub fn amplitude_magnitude_consensus(
  observations : Array[StationAmplitudeObservation],
) -> Double {
  let values = observations.map(item => item.local_magnitude)
  let weights = observations.map(item => item.confidence.max(0.0))
  let mut total = 0.0
  let mut weight = 0.0
  for index = 0; index < values.length(); index = index + 1 {
    total += values[index] * weights[index]
    weight += weights[index]
  }
  if weight <= 0.0 {
    median_samples(values)
  } else {
    total / weight
  }
}

///|
/// Return unique station names in first-seen order.
pub fn unique_station_names(
  observations : Array[StationAmplitudeObservation],
) -> Array[String] {
  let result : Array[String] = Array::new()
  for observation in observations {
    if !result.contains(observation.station) {
      result.push(observation.station)
    }
  }
  result
}

///|
/// Compute spatial coverage and amplitude spread.
pub fn summarize_station_amplitudes(
  observations : Array[StationAmplitudeObservation],
) -> Array[Double] {
  let distances = observations.map(item => item.distance_km)
  let amplitudes = observations.map(item => item.amplitude)
  [
    mean_samples(distances),
    maximum_sample(distances),
    standard_deviation(amplitudes),
    amplitude_magnitude_consensus(observations),
  ]
}

///|
/// Compute event metrics from network events and station observations.
pub fn network_event_metrics(
  events : Array[NetworkEvent],
  observations : Array[StationAmplitudeObservation],
) -> NetworkEventMetrics {
  let unique = unique_station_names(observations)
  let coverage = summarize_station_amplitudes(observations)
  let origins = events.map(event => event.origin_time)
  let magnitudes = events.map(event => event.magnitude)
  let spread = standard_deviation(origins)
  let amplitude_spread = coverage[2]
  let station_score = (unique.length().to_double() / 4.0).min(1.0)
  let coherence_score = @math.exp(-spread / 2.0)
  let amplitude_score = @math.exp(
    -amplitude_spread /
    (mean_samples(observations.map(item => item.amplitude.abs())) + 1.0e-12),
  )
  {
    station_count: observations.length(),
    unique_station_count: unique.length(),
    mean_distance_km: coverage[0],
    maximum_distance_km: coverage[1],
    origin_spread_seconds: spread,
    amplitude_spread,
    magnitude_consensus: if magnitudes.is_empty() {
      0.0
    } else {
      median_samples(magnitudes)
    },
    network_score: (0.45 * station_score +
    0.35 * coherence_score +
    0.2 * amplitude_score).min(1.0),
  }
}

///|
/// Calculate expected station count from a circular network radius.
pub fn expected_station_coverage(
  stations : Array[StationPosition],
  source : StationPosition,
  radius_km : Double,
) -> Double {
  if stations.is_empty() {
    0.0
  } else {
    stations
    .filter(station => surface_distance_km(source, station) <= radius_km.abs())
    .length()
    .to_double() /
    stations.length().to_double()
  }
}

///|
/// Estimate network detection probability from station quality scores.
pub fn network_detection_probability(qualities : Array[Double]) -> Double {
  if qualities.is_empty() {
    0.0
  } else {
    let mut miss_probability = 1.0
    for quality in qualities {
      miss_probability *= 1.0 - quality.max(0.0).min(1.0)
    }
    1.0 - miss_probability
  }
}

///|
/// Format network metrics for the event report.
pub fn network_event_metrics_text(metrics : NetworkEventMetrics) -> String {
  "stations=" +
  metrics.station_count.to_string() +
  " unique=" +
  metrics.unique_station_count.to_string() +
  " mean_distance=" +
  metrics.mean_distance_km.to_string() +
  " max_distance=" +
  metrics.maximum_distance_km.to_string() +
  " origin_spread=" +
  metrics.origin_spread_seconds.to_string() +
  " amplitude_spread=" +
  metrics.amplitude_spread.to_string() +
  " magnitude=" +
  metrics.magnitude_consensus.to_string() +
  " score=" +
  metrics.network_score.to_string()
}