///|
/// A fixed nonzero package capacity and exact price per package.
pub struct PackagePlan {
  priv id : String
  priv version : String
  priv metric : String
  priv unit : String
  priv currency : String
  priv size : BigInt
  priv price : BigInt
}

///|
pub(all) struct PackageEvidence {
  plan_id : String
  plan_version : String
  currency : String
  unit : String
  usage : Usage
  package_size : String
  package_price : String
  packages : String
  unused_units : String
  total : String
  settlement : Settlement
} derive(Debug, Eq)

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

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

///|
pub fn compile_package(
  id~ : String,
  version~ : String,
  metric~ : String,
  unit~ : String,
  currency~ : String,
  package_size~ : String,
  package_price~ : String,
) -> PackagePlan raise PricingError {
  label(id, "package.id")
  label(version, "package.version")
  label(metric, "package.metric")
  label(unit, "package.unit")
  check_currency(currency)
  let size = natural(package_size, "package.package_size")
  if size == 0N {
    invalid(
      "size", "package.package_size", "package size must be a positive integer",
    )
  }
  {
    id,
    version,
    metric,
    unit,
    currency,
    size,
    price: decimal(package_price, "package.package_price"),
  }
}

///|
fn package_quantity(
  plan : PackagePlan,
  usage : Usage,
) -> BigInt raise PricingError {
  check_usage(usage)
  if usage.metric != plan.metric {
    invalid("metric", "usage.metric", "expected metric '\{plan.metric}'")
  }
  natural(usage.quantity, "usage.quantity")
}

///|
/// Round capacity up to full packages, then settle the exact package total once.
///
/// ```mbt check
/// test {
///   let p = @jieheng.compile_package(
///     id="seats",
///     version="v1",
///     metric="users",
///     unit="user",
///     currency="USD",
///     package_size="5",
///     package_price="10",
///   )
///   let e = @jieheng.rate_packages(
///     p,
///     { customer: "acme", metric: "users", period: "2026-10", quantity: "6", },
///     scale=2,
///     rounding=HalfEven,
///   )
///   inspect(e.packages, content="2")
///   inspect(e.unused_units, content="4")
///   inspect(e.settlement.amount, content="20.00")
/// }
/// ```
pub fn rate_packages(
  plan : PackagePlan,
  usage : Usage,
  scale~ : Int32,
  rounding~ : Rounding,
) -> PackageEvidence raise PricingError {
  let quantity = package_quantity(plan, usage)
  let packages = (quantity + plan.size - 1N) / plan.size
  let total = format_atoms(packages * plan.price)
  let record = {
    plan_id: plan.id,
    plan_version: plan.version,
    currency: plan.currency,
    unit: plan.unit,
    usage,
    package_size: plan.size.to_string(),
    package_price: format_atoms(plan.price),
    packages: packages.to_string(),
    unused_units: (packages * plan.size - quantity).to_string(),
    total,
    settlement: settle_total(plan.currency, total, scale~, rounding~),
  }
  verify_packages(plan, usage, record, scale~, rounding~)
  record
}

///|
/// Independently check capacity conservation and minimality without repeating
/// the producer's ceiling division, then check exact price and rounding interval.
pub fn verify_packages(
  plan : PackagePlan,
  usage : Usage,
  record : PackageEvidence,
  scale~ : Int32,
  rounding~ : Rounding,
) -> Unit raise PricingError {
  let quantity = package_quantity(plan, usage)
  if record.plan_id != plan.id ||
    record.plan_version != plan.version ||
    record.currency != plan.currency ||
    record.unit != plan.unit ||
    record.usage != usage ||
    record.package_size != plan.size.to_string() ||
    record.package_price != format_atoms(plan.price) {
    invalid(
      "identity", "packages", "plan, usage, package capacity and price must match trusted inputs",
    )
  }
  let count = natural(record.packages, "packages.packages")
  let unused = natural(record.unused_units, "packages.unused_units")
  if count * plan.size != quantity + unused || unused >= plan.size {
    invalid(
      "capacity", "packages.unused_units", "purchased capacity must equal usage plus unused units, with unused capacity smaller than one package",
    )
  }
  if record.total != format_atoms(count * plan.price) {
    invalid(
      "total", "packages.total", "expected exact package count multiplied by price per package",
    )
  }
  verify_total_settlement(
    plan.currency,
    record.total,
    record.settlement,
    scale~,
    rounding~,
  )
}