///|
pub fn encode_bill_comparison(
  report : BillComparison,
) -> String raise PricingError {
  bounded_json(bill_comparison_json(report))
}

///|
fn bill_comparison_json(report : BillComparison) -> Json raise PricingError {
  if report.changes.length() == 0 || report.changes.length() > 64 {
    invalid(
      "size", "bill_comparison.changes", "expected 1 to 64 metric changes",
    )
  }
  let changes : Array[Json] = report.changes.map(c => {
    "metric": c.metric,
    "unit": c.unit,
    "baseline_index": c.baseline_index,
    "candidate_index": c.candidate_index,
    "usage_delta": c.usage_delta,
    "top_up_delta": c.top_up_delta,
    "exact_delta": c.exact_delta,
    "settled_delta": c.settled_delta,
  })
  let json : Json = {
    "schema": "jieheng.bill-comparison.v1",
    "baseline": bill_json(report.baseline),
    "candidate": bill_json(report.candidate),
    "changes": changes,
    "exact_delta": report.exact_delta,
    "settled_delta": report.settled_delta,
  }
  json
}

///|
/// Strict structural decode; verify_bill_comparison requires both trusted manifests.
pub fn decode_bill_comparison(
  text : String,
) -> BillComparison raise PricingError {
  bill_comparison_from_json(parse_document(text))
}

///|
fn bill_comparison_from_json(value : Json) -> BillComparison raise PricingError {
  let r = fields(
    value,
    [
      "schema", "baseline", "candidate", "changes", "exact_delta", "settled_delta",
    ],
    "bill_comparison",
  )
  expect_value(r, "schema", "jieheng.bill-comparison.v1", "bill_comparison")
  let changes : Array[BillChange] = []
  for i, v in bounded_items(r["changes"], 64, "bill_comparison.changes") {
    let path = "bill_comparison.changes[\{i}]"
    let c = fields(
      v,
      [
        "metric", "unit", "baseline_index", "candidate_index", "usage_delta", "top_up_delta",
        "exact_delta", "settled_delta",
      ],
      path,
    )
    changes.push({
      metric: text_value(c["metric"], path + ".metric"),
      unit: text_value(c["unit"], path + ".unit"),
      baseline_index: small_integer(
        c["baseline_index"],
        63,
        path + ".baseline_index",
      ),
      candidate_index: small_integer(
        c["candidate_index"],
        63,
        path + ".candidate_index",
      ),
      usage_delta: text_value(c["usage_delta"], path + ".usage_delta"),
      top_up_delta: text_value(c["top_up_delta"], path + ".top_up_delta"),
      exact_delta: text_value(c["exact_delta"], path + ".exact_delta"),
      settled_delta: text_value(c["settled_delta"], path + ".settled_delta"),
    })
  }
  {
    baseline: bill_from_json(r["baseline"]),
    candidate: bill_from_json(r["candidate"]),
    changes,
    exact_delta: text_value(r["exact_delta"], "bill_comparison.exact_delta"),
    settled_delta: text_value(
      r["settled_delta"],
      "bill_comparison.settled_delta",
    ),
  }
}