///|
/// Converts alert severity into deterministic notification channels.
pub struct RoutingDecision {
  metric : String
  channel : String
  priority : Int
  mut acknowledged : Bool
  reason : String
}

///|
pub fn RoutingDecision::new(
  metric : String,
  channel : String,
  priority : Int,
  reason : String,
) -> RoutingDecision {
  { metric, channel, priority, acknowledged: false, reason }
}

///|
pub fn RoutingDecision::acknowledge(self : RoutingDecision) -> Unit {
  self.acknowledged = true
}

///|
pub fn RoutingDecision::is_page(self : RoutingDecision) -> Bool {
  self.channel == "page"
}

///|
pub fn RoutingDecision::summary(self : RoutingDecision) -> String {
  self.metric +
  ":" +
  self.channel +
  ":priority=" +
  self.priority.to_string() +
  ":" +
  self.reason
}

///|
pub fn route_alert(alert : AlertEvent) -> RoutingDecision {
  match alert.point.severity {
    Informational =>
      RoutingDecision::new(alert.metric, "log", 1, "record for trend analysis")
    Warning =>
      RoutingDecision::new(
        alert.metric,
        "ticket",
        2,
        "inspect within the current shift",
      )
    Critical =>
      RoutingDecision::new(alert.metric, "page", 3, "wake the service owner")
  }
}

///|
pub fn route_alerts(alerts : Array[AlertEvent]) -> Array[RoutingDecision] {
  let result : Array[RoutingDecision] = []
  for alert in alerts {
    if !alert.suppressed {
      result.push(route_alert(alert))
    }
  }
  result
}

///|
pub fn page_count(decisions : Array[RoutingDecision]) -> Int {
  let mut count = 0
  for decision in decisions {
    if decision.is_page() {
      count += 1
    }
  }
  count
}

///|
pub fn highest_priority(decisions : Array[RoutingDecision]) -> Int {
  let mut priority = 0
  for decision in decisions {
    if decision.priority > priority {
      priority = decision.priority
    }
  }
  priority
}

///|
pub fn routing_summary(decisions : Array[RoutingDecision]) -> String {
  let mut output = "channel,priority,metric\n"
  for decision in decisions {
    output = output +
      decision.channel +
      "," +
      decision.priority.to_string() +
      "," +
      decision.metric +
      "\n"
  }
  output
}

///|
pub struct RoutingBudget {
  capacity : Int
  mut used : Int
}

///|
pub fn RoutingBudget::new(capacity? : Int = 10) -> RoutingBudget {
  { capacity: if capacity < 0 { 0 } else { capacity }, used: 0 }
}

///|
pub fn RoutingBudget::allow(
  self : RoutingBudget,
  decision : RoutingDecision,
) -> Bool {
  if decision.priority >= 3 || self.used < self.capacity {
    self.used += 1
    true
  } else {
    false
  }
}

///|
pub fn RoutingBudget::used(self : RoutingBudget) -> Int {
  self.used
}

///|
pub fn RoutingBudget::remaining(self : RoutingBudget) -> Int {
  if self.capacity > self.used {
    self.capacity - self.used
  } else {
    0
  }
}