///|
fn probability_value(v : Json) -> Double raise FaultError {
  match v {
    Number(p, ..) => {
      valid_probability(p)
      p
    }
    _ => raise Invalid("probability must be a number")
  }
}

///|
pub fn run_json(request : String) -> String {
  try {
    guard request.length() <= 1000000 else {
      raise Limit("request exceeds 1000000 UTF-16 units")
    }
    let o = obj(@json.parse(request))
    let model = obj(required(o, "model"))
    let events = arr(required(model, "events")).map(v => {
      let e = obj(v)
      {
        id: txt(required(e, "id")),
        probability: probability_value(required(e, "probability")),
      }
    })
    let gates = arr(required(model, "gates")).map(v => {
      let g = obj(v)
      let kind = match txt(required(g, "kind")) {
        "and" => And
        "or" => Or
        "vote" => Vote(num(required(g, "threshold")))
        _ => raise Invalid("gate kind must be and/or/vote")
      }
      {
        id: txt(required(g, "id")),
        kind,
        inputs: arr(required(g, "inputs")).map(txt),
      }
    })
    let tree = compile(
      events,
      gates,
      txt(required(model, "top")),
      arr(o.get("order").unwrap_or(Json::array([]))).map(txt),
    )
    let overrides : Map[String, Double] = Map([])
    for k, v in obj(o.get("overrides").unwrap_or(jo([]))) {
      overrides[k] = probability_value(v)
    }
    let forced : Map[String, Bool] = Map([])
    for k, v in obj(o.get("forced").unwrap_or(jo([]))) {
      forced[k] = match v {
        True => true
        False => false
        _ => raise Invalid("forced event state must be boolean")
      }
    }
    let command = txt(o.get("command").unwrap_or(Json::string("analyze")))
    let result = match command {
      "analyze" =>
        @json.to_json(
          tree.analyze(
            overrides,
            forced,
            num(o.get("max_cut_sets").unwrap_or(jn(5000))),
          ),
        )
      "probability" => Json::number(tree.probability(overrides, forced))
      _ => raise Invalid("unknown command")
    }
    jo([("ok", Json::boolean(true)), ("result", result)]).stringify()
  } catch {
    e => {
      let (code, message) = match e {
        FaultError::Invalid(s) => ("invalid", s)
        FaultError::Limit(s) => ("limit", s)
        _ => ("json", "invalid JSON request")
      }
      jo([
        ("ok", Json::boolean(false)),
        ("code", Json::string(code)),
        ("error", Json::string(message)),
      ]).stringify()
    }
  }
}