///|
/// A service-level objective snapshot derived from one report.
pub struct SloSnapshot {
  availability_percent : Int
  error_budget_percent : Int
  invalid_input_percent : Int
  query_request_percent : Int
  total_requests : Int
  server_errors : Int
}

///|
/// Return the report's core service-level indicators.
pub fn Report::slo_snapshot(self : Report) -> SloSnapshot {
  SloSnapshot::{
    availability_percent: self.success_rate_percent(),
    error_budget_percent: 100 - self.error_rate_percent(),
    invalid_input_percent: self.invalid_rate_percent(),
    query_request_percent: self.query_request_percent(),
    total_requests: self.requests,
    server_errors: self.server_error_requests(),
  }
}

///|
/// Check both an error-rate and malformed-input budget.
pub fn Report::within_budgets(
  self : Report,
  max_error_rate_percent : Int,
  max_invalid_rate_percent : Int,
) -> Bool {
  self.error_rate_percent() <= max_error_rate_percent &&
  self.invalid_rate_percent() <= max_invalid_rate_percent
}

///|
/// Return the number of distinct user agents.
pub fn Report::unique_user_agent_count(self : Report) -> Int {
  map_cardinality(self.user_agent_counts)
}

///|
/// Return the number of distinct referrers.
pub fn Report::unique_referrer_count(self : Report) -> Int {
  map_cardinality(self.referrer_counts)
}

///|
/// Return the number of distinct query parameter names.
pub fn Report::unique_query_key_count(self : Report) -> Int {
  map_cardinality(self.query_key_counts)
}

///|
/// Return the number of protocols observed.
pub fn Report::unique_protocol_count(self : Report) -> Int {
  map_cardinality(self.protocol_counts)
}

///|
/// Return the number of routes whose error rate exceeds a threshold.
pub fn Report::unhealthy_route_count(
  self : Report,
  threshold_percent : Int,
) -> Int {
  let mut total = 0
  for route, requests in self.route_counts {
    let errors = match self.route_error_counts.get(route) {
      Some(value) => value
      None => 0
    }
    if requests > 0 && errors * 100 / requests > threshold_percent {
      total += 1
    }
  }
  total
}

///|
/// Return routes ranked by error count, then route name.
pub fn Report::top_error_routes(
  self : Report,
  limit : Int,
) -> Array[(String, Int)] {
  let entries : Array[(String, Int)] = []
  for route, count in self.route_error_counts {
    entries.push((route, count))
  }
  entries.sort_by(fn(a, b) {
    if a.1 > b.1 {
      -1
    } else if a.1 < b.1 {
      1
    } else {
      a.0.compare(b.0)
    }
  })
  take_entries(entries, limit)
}

///|
/// Return routes ranked by transferred bytes.
pub fn Report::largest_routes(
  self : Report,
  limit : Int,
) -> Array[(String, Int)] {
  let entries : Array[(String, Int)] = []
  for route, bytes in self.route_bytes_counts {
    entries.push((route, bytes))
  }
  entries.sort_by(fn(a, b) {
    if a.1 > b.1 {
      -1
    } else if a.1 < b.1 {
      1
    } else {
      a.0.compare(b.0)
    }
  })
  take_entries(entries, limit)
}

///|
/// Return the percentage of requests at one route depth.
pub fn Report::route_depth_percent(self : Report, depth : Int) -> Int {
  let count = match self.route_depth_counts().get(depth) {
    Some(value) => value
    None => 0
  }
  if self.requests == 0 {
    0
  } else {
    count * 100 / self.requests
  }
}

///|
/// Render a compact environment-style key/value snapshot.
pub fn Report::to_env(self : Report, prefix : String) -> String {
  let name = sanitize_metric_name(prefix).to_upper()
  let snapshot = self.slo_snapshot()
  let mut out = "\{name}_REQUESTS=\{snapshot.total_requests}\n"
  out = out + "\{name}_ERROR_RATE_PERCENT=\{self.error_rate_percent()}\n"
  out = out + "\{name}_SUCCESS_RATE_PERCENT=\{self.success_rate_percent()}\n"
  out = out + "\{name}_INVALID_RATE_PERCENT=\{snapshot.invalid_input_percent}\n"
  out = out + "\{name}_SERVER_ERRORS=\{snapshot.server_errors}\n"
  out = out +
    "\{name}_QUERY_REQUEST_PERCENT=\{snapshot.query_request_percent}\n"
  out = out + "\{name}_UNIQUE_ROUTES=\{self.unique_path_count()}\n"
  out + "\{name}_UNHEALTHY_ROUTES=\{self.unhealthy_route_count(20)}\n"
}

///|
/// Render a deterministic JSON object containing only top-level health data.
pub fn Report::to_summary_json(self : Report) -> String {
  let snapshot = self.slo_snapshot()
  "{\"requests\":\{snapshot.total_requests},\"bytes\":\{self.total_bytes},\"error_rate_percent\":\{self.error_rate_percent()},\"success_rate_percent\":\{snapshot.availability_percent},\"invalid_rate_percent\":\{snapshot.invalid_input_percent},\"query_request_percent\":\{snapshot.query_request_percent},\"server_errors\":\{snapshot.server_errors},\"unique_routes\":\{self.unique_path_count()},\"unhealthy_routes\":\{self.unhealthy_route_count(20)}}"
}

///|
fn[K] map_cardinality(counts : Map[K, Int]) -> Int {
  let mut total = 0
  for _, _ in counts {
    total += 1
  }
  total
}

///|
fn take_entries(
  entries : Array[(String, Int)],
  limit : Int,
) -> Array[(String, Int)] {
  let result : Array[(String, Int)] = []
  let actual_limit = if limit < 0 { 0 } else { limit }
  let mut index = 0
  for entry in entries {
    if index < actual_limit {
      result.push(entry)
    }
    index += 1
  }
  result
}