///|
pub(all) struct BudgetProjection {
  target : SloTarget
  current : BudgetReport
  projected_bad : Int
  projected_total : Int
  projected_consumed_bp : Int
  remaining_after_projection : Int
  status : String
} derive(Eq, Debug)

///|
pub fn project_budget(
  target : SloTarget,
  current : RequestWindow,
  additional_total : Int,
  additional_bad : Int,
) -> BudgetProjection {
  let current_report = evaluate_budget(target, current)
  let safe_total = if additional_total > 0 { additional_total } else { 0 }
  let safe_bad = if additional_bad > 0 {
    if additional_bad > safe_total {
      safe_total
    } else {
      additional_bad
    }
  } else {
    0
  }
  let projected_total = current.total + safe_total
  let projected_bad = current.bad() + safe_bad
  let allowed = projected_total * target.allowed_error_bp() / 10000
  let consumed = if allowed <= 0 {
    if projected_bad > 0 {
      10000
    } else {
      0
    }
  } else {
    projected_bad * 10000 / allowed
  }
  let remaining = allowed - projected_bad
  let status = if remaining < 0 {
    "exhausted"
  } else if consumed >= 10000 {
    "at_limit"
  } else if consumed >= 8000 {
    "near_limit"
  } else {
    "within_budget"
  }
  {
    target,
    current: current_report,
    projected_bad,
    projected_total,
    projected_consumed_bp: consumed,
    remaining_after_projection: remaining,
    status,
  }
}

///|
pub fn BudgetProjection::safe(self : BudgetProjection) -> Bool {
  self.remaining_after_projection >= 0
}

///|
pub fn BudgetProjection::status_name(self : BudgetProjection) -> String {
  self.status
}

///|
pub fn BudgetProjection::to_json(self : BudgetProjection) -> String {
  "{\"projected_bad\":\{self.projected_bad},\"projected_total\":\{self.projected_total},\"projected_consumed_bp\":\{self.projected_consumed_bp},\"remaining_after_projection\":\{self.remaining_after_projection},\"status\":\"\{self.status}\"}"
}

///|
pub fn remaining_budget_ratio_bp(report : BudgetReport) -> Int {
  if report.allowed_bad <= 0 {
    if report.actual_bad == 0 {
      10000
    } else {
      0
    }
  } else if report.remaining_bad <= 0 {
    0
  } else {
    report.remaining_bad * 10000 / report.allowed_bad
  }
}

///|
pub fn budget_headroom(report : BudgetReport) -> Int {
  if report.remaining_bad > 0 {
    report.remaining_bad
  } else {
    0
  }
}

///|
pub fn compliance_score(
  target : SloTarget,
  windows : Array[RequestWindow],
) -> Int {
  if windows.length() == 0 {
    0
  } else {
    let mut compliant = 0
    for window in windows {
      if window.availability_bp() >= target.target_bp {
        compliant = compliant + 1
      }
    }
    compliant * 10000 / windows.length()
  }
}

///|
pub fn compliance_label(score_bp : Int) -> String {
  if score_bp >= 9900 {
    "excellent"
  } else if score_bp >= 9500 {
    "good"
  } else if score_bp >= 8000 {
    "at_risk"
  } else {
    "failing"
  }
}

///|
pub fn target_gap_bp(target : SloTarget, window : RequestWindow) -> Int {
  target.target_bp - window.availability_bp()
}

///|
pub fn target_is_met(target : SloTarget, window : RequestWindow) -> Bool {
  target_gap_bp(target, window) <= 0
}

///|
pub fn best_window(windows : Array[RequestWindow]) -> RequestWindow {
  if windows.length() == 0 {
    RequestWindow::new("empty", 1, 0, 0)
  } else {
    let mut selected = windows[0]
    for window in windows {
      if window.availability_bp() > selected.availability_bp() {
        selected = window
      }
    }
    selected
  }
}

///|
pub fn worst_window(windows : Array[RequestWindow]) -> RequestWindow {
  if windows.length() == 0 {
    RequestWindow::new("empty", 1, 0, 0)
  } else {
    let mut selected = windows[0]
    for window in windows {
      if window.availability_bp() < selected.availability_bp() {
        selected = window
      }
    }
    selected
  }
}

///|
pub fn BudgetReport::budget_status(self : BudgetReport) -> String {
  if self.remaining_bad < 0 {
    "exhausted"
  } else if self.consumed_bp >= 8000 {
    "near_limit"
  } else {
    "healthy"
  }
}

///|
pub fn BudgetReport::remaining_ratio_bp(self : BudgetReport) -> Int {
  remaining_budget_ratio_bp(self)
}

///|
pub fn budget_status_priority(status : String) -> Int {
  match status {
    "exhausted" => 3
    "near_limit" => 2
    "at_limit" => 2
    "healthy" => 0
    "within_budget" => 0
    _ => 1
  }
}