///|
pub(all) struct MetricWindow {
  start_minute : Int
  end_minute : Int
  total_samples : Int
  max_error_per_million : Int
  max_latency_p95_ms : Int
  max_saturation_bp : Int
  synthetic_pass_count : Int
  dependency_failures : Int
} derive(Eq, Debug)

///|
pub(all) struct TrendDelta {
  sample_delta : Int
  error_delta : Int
  latency_delta_ms : Int
  saturation_delta_bp : Int
  dependency_failure_delta : Int
} derive(Eq, Debug)

///|
pub fn metric_window(
  metrics : Array[MetricsSnapshot],
  start_minute : Int,
  end_minute : Int,
) -> MetricWindow {
  let mut samples = 0
  let mut max_error = 0
  let mut max_latency = 0
  let mut max_saturation = 0
  let mut synthetic_pass = 0
  let mut dependency_failures = 0
  for item in metrics {
    if item.minute >= start_minute && item.minute <= end_minute {
      samples += item.sample_count
      if item.error_per_million > max_error {
        max_error = item.error_per_million
      }
      if item.latency_p95_ms > max_latency {
        max_latency = item.latency_p95_ms
      }
      if item.saturation_bp > max_saturation {
        max_saturation = item.saturation_bp
      }
      if item.synthetic_pass {
        synthetic_pass += 1
      }
      dependency_failures += item.dependency_failures
    }
  }
  {
    start_minute,
    end_minute,
    total_samples: samples,
    max_error_per_million: max_error,
    max_latency_p95_ms: max_latency,
    max_saturation_bp: max_saturation,
    synthetic_pass_count: synthetic_pass,
    dependency_failures,
  }
}

///|
pub fn window_to_snapshot(window : MetricWindow) -> MetricsSnapshot {
  MetricsSnapshot(
    minute=window.end_minute,
    sample_count=window.total_samples,
    error_per_million=window.max_error_per_million,
    latency_p95_ms=window.max_latency_p95_ms,
    saturation_bp=window.max_saturation_bp,
    synthetic_pass=window.synthetic_pass_count > 0,
    dependency_failures=window.dependency_failures,
  )
}

///|
pub fn trend_delta(
  previous : MetricWindow,
  current : MetricWindow,
) -> TrendDelta {
  {
    sample_delta: current.total_samples - previous.total_samples,
    error_delta: current.max_error_per_million - previous.max_error_per_million,
    latency_delta_ms: current.max_latency_p95_ms - previous.max_latency_p95_ms,
    saturation_delta_bp: current.max_saturation_bp - previous.max_saturation_bp,
    dependency_failure_delta: current.dependency_failures -
    previous.dependency_failures,
  }
}

///|
pub fn metric_window_gate(
  budget : RiskBudget,
  previous : MetricWindow,
  current : MetricWindow,
  blast : BlastRadius,
) -> GateResult {
  metric_trend_gate(
    budget,
    window_to_snapshot(previous),
    window_to_snapshot(current),
    blast,
  )
}

///|
pub fn window_markdown(window : MetricWindow) -> String {
  let out = StringBuilder()
  out.write_string("## Metric Window\n\n")
  out.write_string("- Start minute: " + window.start_minute.to_string() + "\n")
  out.write_string("- End minute: " + window.end_minute.to_string() + "\n")
  out.write_string("- Samples: " + window.total_samples.to_string() + "\n")
  out.write_string(
    "- Max error: " + window.max_error_per_million.to_string() + " ppm\n",
  )
  out.write_string(
    "- Max p95 latency: " + window.max_latency_p95_ms.to_string() + " ms\n",
  )
  out.write_string(
    "- Max saturation: " + percent_text(window.max_saturation_bp) + "\n",
  )
  out.write_string(
    "- Dependency failures: " + window.dependency_failures.to_string() + "\n",
  )
  out.to_string()
}

///|
pub fn window_json(window : MetricWindow) -> String {
  let out = StringBuilder()
  out.write_string("{")
  out.write_string("\"start\":" + window.start_minute.to_string() + ",")
  out.write_string("\"end\":" + window.end_minute.to_string() + ",")
  out.write_string("\"samples\":" + window.total_samples.to_string() + ",")
  out.write_string(
    "\"max_error_ppm\":" + window.max_error_per_million.to_string() + ",",
  )
  out.write_string(
    "\"max_latency_ms\":" + window.max_latency_p95_ms.to_string() + ",",
  )
  out.write_string(
    "\"max_saturation_bp\":" + window.max_saturation_bp.to_string() + ",",
  )
  out.write_string(
    "\"dependency_failures\":" + window.dependency_failures.to_string(),
  )
  out.write_string("}")
  out.to_string()
}

///|
pub fn latest_metric(metrics : Array[MetricsSnapshot]) -> MetricsSnapshot {
  let mut latest = MetricsSnapshot::MetricsSnapshot()
  for item in metrics {
    if item.minute >= latest.minute {
      latest = item
    }
  }
  latest
}

///|
pub fn max_error_metric(metrics : Array[MetricsSnapshot]) -> MetricsSnapshot {
  let mut worst = MetricsSnapshot::MetricsSnapshot()
  for item in metrics {
    if item.error_per_million >= worst.error_per_million {
      worst = item
    }
  }
  worst
}

///|
pub fn max_latency_metric(metrics : Array[MetricsSnapshot]) -> MetricsSnapshot {
  let mut worst = MetricsSnapshot::MetricsSnapshot()
  for item in metrics {
    if item.latency_p95_ms >= worst.latency_p95_ms {
      worst = item
    }
  }
  worst
}

///|
pub fn max_saturation_metric(
  metrics : Array[MetricsSnapshot],
) -> MetricsSnapshot {
  let mut worst = MetricsSnapshot::MetricsSnapshot()
  for item in metrics {
    if item.saturation_bp >= worst.saturation_bp {
      worst = item
    }
  }
  worst
}

///|
pub fn all_synthetic_pass(metrics : Array[MetricsSnapshot]) -> Bool {
  if metrics.length() == 0 {
    return false
  }
  for item in metrics {
    if !item.synthetic_pass {
      return false
    }
  }
  true
}

///|
pub fn total_dependency_failures(metrics : Array[MetricsSnapshot]) -> Int {
  let mut total = 0
  for item in metrics {
    total += item.dependency_failures
  }
  total
}