///|
/// Candidate minus baseline, for one identical aggregated usage snapshot.
/// Deltas are signed exact decimal strings. Both sides retain complete evidence.
pub(all) struct Comparison {
  baseline : Evidence
  candidate : Evidence
  exact_delta : String
  settled_delta : String
} derive(Debug, Eq)

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

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

///|
/// Compare compatible plans at the same usage and settlement policy.
/// Settled delta is the difference of two settled amounts, not a rounded delta.
///
/// ```mbt check
/// test {
///   let before = @jieheng.compile_plan(
///     id="api",
///     version="v1",
///     metric="calls",
///     unit="call",
///     currency="USD",
///     tiers=[{ up_to: None, unit_price: "0.004", }],
///   )
///   let after = @jieheng.compile_plan(
///     id="api",
///     version="v2",
///     metric="calls",
///     unit="call",
///     currency="USD",
///     tiers=[{ up_to: None, unit_price: "0.006", }],
///   )
///   let report = @jieheng.compare_plans(
///     before,
///     after,
///     { customer: "acme", metric: "calls", period: "2026-10", quantity: "1", },
///     scale=2,
///     rounding=HalfEven,
///   )
///   inspect(report.exact_delta, content="0.002")
///   inspect(report.settled_delta, content="0.01")
/// }
/// ```
pub fn compare_plans(
  baseline : Plan,
  candidate : Plan,
  usage : Usage,
  scale~ : Int32,
  rounding~ : Rounding,
) -> Comparison raise PricingError {
  comparable(baseline, candidate)
  let before = rate(baseline, usage)
  let after = rate(candidate, usage)
  let before_settled = settle(before, scale~, rounding~)
  let after_settled = settle(after, scale~, rounding~)
  let report = {
    baseline: { calculation: before, settlement: before_settled, },
    candidate: { calculation: after, settlement: after_settled, },
    exact_delta: format_atoms(
      amount_atoms(after.total, "candidate.total") -
      amount_atoms(before.total, "baseline.total"),
    ),
    settled_delta: format_atoms(
      amount_atoms(after_settled.amount, "candidate.amount") -
      amount_atoms(before_settled.amount, "baseline.amount"),
    ),
  }
  verify_comparison(baseline, candidate, usage, report, scale~, rounding~)
  report
}

///|
fn comparable(baseline : Plan, candidate : Plan) -> Unit raise PricingError {
  if baseline.currency != candidate.currency {
    invalid(
      "currency", "comparison", "both plans must use the same currency; no FX conversion is performed",
    )
  }
  if baseline.metric != candidate.metric || baseline.unit != candidate.unit {
    invalid(
      "unit", "comparison", "both plans must use the same metric and unit; no unit conversion is performed",
    )
  }
}

///|
/// Validate both sides against trusted inputs and recompute signed differences.
pub fn verify_comparison(
  baseline : Plan,
  candidate : Plan,
  usage : Usage,
  report : Comparison,
  scale~ : Int32,
  rounding~ : Rounding,
) -> Unit raise PricingError {
  comparable(baseline, candidate)
  verify_evidence(baseline, usage, report.baseline, scale~, rounding~) catch {
    Invalid(code~, path~, message~) =>
      raise Invalid(code~, path="comparison.baseline." + path, message~)
  }
  verify_evidence(candidate, usage, report.candidate, scale~, rounding~) catch {
    Invalid(code~, path~, message~) =>
      raise Invalid(code~, path="comparison.candidate." + path, message~)
  }
  let expected = amount_atoms(
      report.candidate.calculation.total,
      "candidate.total",
    ) -
    amount_atoms(report.baseline.calculation.total, "baseline.total")
  if report.exact_delta != format_atoms(expected) {
    invalid(
      "delta", "comparison.exact_delta", "expected candidate exact total minus baseline exact total",
    )
  }
  let expected_settled = amount_atoms(
      report.candidate.settlement.amount,
      "candidate.amount",
    ) -
    amount_atoms(report.baseline.settlement.amount, "baseline.amount")
  if report.settled_delta != format_atoms(expected_settled) {
    invalid(
      "delta", "comparison.settled_delta", "expected candidate settled amount minus baseline settled amount",
    )
  }
}