///|
/// A finite procurement request, without an ignored or guessed usage quantity.
pub(all) struct BudgetRequest {
  customer : String
  period : String
  budget : String
  limit : String
} derive(Debug, Eq)

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

///|
pub extend BudgetRequest with Eq::{equal, not_equal}

///|
/// Maximum affordable quantity within the explicit limit, with complete evidence.
/// For Volume, smaller quantities need not all be affordable.
pub(all) struct BudgetQuote {
  request : BudgetRequest
  evidence : Evidence
} derive(Debug, Eq)

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

///|
pub extend BudgetQuote with Eq::{equal, not_equal}

///|
fn check_budget(request : BudgetRequest) -> Unit raise PricingError {
  label(request.customer, "budget.customer")
  label(request.period, "budget.period")
  ignore(amount_atoms(request.budget, "budget.budget"))
  ignore(natural(request.limit, "budget.limit"))
}

///|
fn budget_usage(
  plan : Plan,
  request : BudgetRequest,
  quantity : BigInt,
) -> Usage {
  {
    customer: request.customer,
    metric: plan.metric,
    period: request.period,
    quantity: quantity.to_string(),
  }
}

///|
fn search_quantity(
  lower : BigInt,
  upper : BigInt,
  fits : (BigInt) -> Bool raise PricingError,
) -> BigInt? raise PricingError {
  if lower > upper || !fits(lower) {
    return None
  }
  let mut lo = lower
  let mut hi = upper
  while lo < hi {
    let mid = (lo + hi + 1N) / 2N
    if fits(mid) {
      lo = mid
    } else {
      hi = mid - 1N
    }
  }
  Some(lo)
}

///|
fn rounded_fits(
  currency : String,
  total : BigInt,
  budget : BigInt,
  scale~ : Int32,
  rounding~ : Rounding,
) -> Bool raise PricingError {
  let settled = settle_total(currency, format_atoms(total), scale~, rounding~)
  let step = BigInt::from_string("1" + "0".repeat(18 - scale))
  // Compare integer minor units directly, including unaffordable probes.
  BigInt::from_string(settled.minor_units) * step <= budget
}

///|
/// Maximize integer quantity subject to settled charge <= budget and q <= limit.
/// Does not apply minimum spend, package rules, taxes or cross-metric budgets.
///
/// ```mbt check
/// test {
///   let p = @jieheng.compile_plan(
///     id="api",
///     version="v1",
///     metric="calls",
///     unit="call",
///     currency="USD",
///     tiers=[{ up_to: None, unit_price: "0.006", }],
///   )
///   let q = @jieheng.quote_budget(
///     p,
///     { customer: "acme", period: "2026-10", budget: "0.01", limit: "100", },
///     scale=2,
///     rounding=HalfEven,
///   )
///   inspect(q.evidence.calculation.usage.quantity, content="2")
///   inspect(q.evidence.settlement.amount, content="0.01")
/// }
/// ```
pub fn quote_budget(
  plan : Plan,
  request : BudgetRequest,
  scale~ : Int32,
  rounding~ : Rounding,
) -> BudgetQuote raise PricingError {
  check_budget(request)
  // Validate scale before any power or division; zero is always affordable.
  ignore(settle_total(plan.currency, "0", scale~, rounding~))
  let budget = amount_atoms(request.budget, "budget.budget")
  let limit = natural(request.limit, "budget.limit")
  let mut best = 0N
  match plan.mode {
    Graduated => {
      let found = search_quantity(0N, limit, q => {
        let calculation = rate(plan, budget_usage(plan, request, q))
        rounded_fits(
          plan.currency,
          amount_atoms(calculation.total, "calculation.total"),
          budget,
          scale~,
          rounding~,
        )
      })
      if found is Some(q) {
        best = q
      }
    }
    Volume => {
      let mut previous = 0N
      for tier in plan.tiers {
        let upper = match tier.up_to {
          Some(n) => if n < limit { n } else { limit }
          None => limit
        }
        let found = search_quantity(previous + 1N, upper, q => {
          rounded_fits(plan.currency, q * tier.price, budget, scale~, rounding~)
        })
        if found is Some(q) {
          best = q
        }
        match tier.up_to {
          Some(n) => previous = n
          None => ()
        }
        if upper == limit {
          break
        }
      }
    }
  }
  let calculation = rate(plan, budget_usage(plan, request, best))
  let quote = {
    request,
    evidence: {
      calculation,
      settlement: settle(calculation, scale~, rounding~),
    },
  }
  verify_budget(plan, request, quote, scale~, rounding~)
  quote
}

///|
/// Exact affordability threshold derived from the rounding intervals. The
/// verifier never calls settle_total, search_quantity or quote_budget.
fn threshold_fits(
  total : BigInt,
  budget : BigInt,
  scale : Int32,
  rounding : Rounding,
) -> Bool {
  let step = BigInt::from_string("1" + "0".repeat(18 - scale))
  let units = budget / step
  match rounding {
    Down => total < (units + 1N) * step
    Up => total <= units * step
    HalfUp => total * 2N < (units * 2N + 1N) * step
    HalfEven => {
      let twice = total * 2N
      let boundary = (units * 2N + 1N) * step
      twice < boundary || (twice == boundary && units % 2N == 0N)
    }
  }
}

///|
/// Check trusted request, allocation, settlement, feasibility and global
/// maximality. For Volume, examine the cheapest larger point of every tier.
pub fn verify_budget(
  plan : Plan,
  request : BudgetRequest,
  quote : BudgetQuote,
  scale~ : Int32,
  rounding~ : Rounding,
) -> Unit raise PricingError {
  check_budget(request)
  if quote.request != request {
    invalid(
      "identity", "budget.request", "quote request must match all trusted request fields",
    )
  }
  let q = natural(quote.evidence.calculation.usage.quantity, "budget.quantity")
  let limit = natural(request.limit, "budget.limit")
  if q > limit {
    invalid(
      "limit", "budget.quantity", "quantity exceeds the trusted finite search limit",
    )
  }
  verify_evidence(
    plan,
    budget_usage(plan, request, q),
    quote.evidence,
    scale~,
    rounding~,
  )
  let budget = amount_atoms(request.budget, "budget.budget")
  if amount_atoms(quote.evidence.settlement.amount, "settlement.amount") >
    budget {
    invalid(
      "budget", "budget.quantity", "settled amount exceeds the trusted budget",
    )
  }
  if q == limit {
    return
  }
  match plan.mode {
    Graduated => {
      // Nonnegative marginal prices imply a monotone total. Verify the next
      // exact amount by adding its one trusted marginal price to verified total.
      let next = q + 1N
      let raw = amount_atoms(
        quote.evidence.calculation.total,
        "calculation.total",
      )
      for tier in plan.tiers {
        if tier.up_to is None || (tier.up_to is Some(n) && next <= n) {
          if threshold_fits(raw + tier.price, budget, scale, rounding) {
            invalid(
              "maximum", "budget.quantity", "a larger quantity remains affordable",
            )
          }
          break
        }
      }
    }
    Volume => {
      let mut previous = 0N
      for tier in plan.tiers {
        let upper = match tier.up_to {
          Some(n) => if n < limit { n } else { limit }
          None => limit
        }
        let lower = if previous > q { previous + 1N } else { q + 1N }
        if lower <= upper &&
          threshold_fits(lower * tier.price, budget, scale, rounding) {
          invalid(
            "maximum", "budget.quantity", "a larger quantity is affordable in this or a later volume tier; lower quantities need not form a feasible prefix",
          )
        }
        match tier.up_to {
          Some(n) => previous = n
          None => ()
        }
        if upper == limit {
          break
        }
      }
    }
  }
}