///|
/// Encode all typed fields in deterministic order; verify before accepting data.
pub fn encode_comparison(report : Comparison) -> String {
  comparison_json(report).stringify()
}

///|
fn comparison_json(report : Comparison) -> Json {
  let json : Json = {
    "schema": "jieheng.comparison.v1",
    "baseline": evidence_json(report.baseline),
    "candidate": evidence_json(report.candidate),
    "exact_delta": report.exact_delta,
    "settled_delta": report.settled_delta,
  }
  json
}

///|
/// Strict structural decoder; semantic acceptance requires verify_comparison.
pub fn decode_comparison(text : String) -> Comparison raise PricingError {
  comparison_from_json(parse_document(text))
}

///|
fn comparison_from_json(value : Json) -> Comparison raise PricingError {
  let object = fields(
    value,
    ["schema", "baseline", "candidate", "exact_delta", "settled_delta"],
    "comparison",
  )
  expect_value(object, "schema", "jieheng.comparison.v1", "comparison")
  {
    baseline: comparison_side(object["baseline"], "baseline"),
    candidate: comparison_side(object["candidate"], "candidate"),
    exact_delta: text_value(object["exact_delta"], "comparison.exact_delta"),
    settled_delta: text_value(
      object["settled_delta"],
      "comparison.settled_delta",
    ),
  }
}

///|
fn comparison_side(value : Json, side : String) -> Evidence raise PricingError {
  evidence_from_json(value) catch {
    Invalid(code~, path~, message~) =>
      raise Invalid(code~, path="comparison." + side + "." + path, message~)
  }
}