///|
/// Scale of the external-arrival vector at which the first utilized center
/// reaches the requested utilization. None means there is no offered traffic.
pub(all) struct NetworkLoadLimit {
  scale : Double?
  bottleneck : Int?
  node_utilizations : Array[Double]
} derive(Debug, ToJson)

///|
pub fn JacksonNetwork::load_limit(
  self : JacksonNetwork,
  maximum_utilization : Double,
) -> NetworkLoadLimit raise QueueError {
  probability(maximum_utilization, "network utilization ceiling")
  if maximum_utilization >= 1.0 {
    raise Invalid("network ceiling must be below one")
  }
  let flows = self.solve_traffic()
  let utilizations = []
  let mut best : Double? = None
  let mut bottleneck : Int? = None
  for i in 0.. 0.0 {
      let scale = checked_ratio(
        maximum_utilization, utilization, "network load factor",
      )
      let replace = match best {
        None => true
        Some(value) => scale < value
      }
      if replace {
        best = Some(scale)
        bottleneck = Some(i)
      }
    }
  }
  { scale: best, bottleneck, node_utilizations: utilizations, }
}

///|
/// Copy a network and scale only its external arrivals, retaining routing and
/// service parameters. This operation validates shapes but permits overload;
/// analyze() remains the strict stability gate.
pub fn JacksonNetwork::scaled_load(
  self : JacksonNetwork,
  factor : Double,
) -> JacksonNetwork raise QueueError {
  self.validate()
  nonnegative(factor, "network load factor")
  let external = self.external.map(x => x * factor)
  let copy : JacksonNetwork = {
    nodes: self.nodes.copy(),
    external,
    routing: self.routing.map(row => row.copy()),
  }
  copy.validate()
  copy
}

///|
pub extend NetworkLoadLimit with Debug::{to_repr}

///|
pub extend NetworkLoadLimit with ToJson::{to_json}