///|
/// Decode a Problem from the documented JSON schema.  Parsing and schema
/// validation are separate so callers can report a useful location/type error
/// before numerical work starts.
pub fn problem_from_json(input : String) -> Result[Problem, SolveError] {
  try {
    let value = @json.parse(input)
    let problem : Problem = @json.from_json(value)
    Ok(problem)
  } catch {
    _ => Err(InvalidInput("input is not valid MoonAqueousSpec JSON"))
  }
}

///|
pub fn problem_to_json(problem : Problem) -> String {
  @json.to_json(problem).stringify()
}

///|
pub fn result_to_json(result : SpeciationResult) -> String {
  @json.to_json(result).stringify()
}

///|
pub fn solve_json(input : String) -> String {
  match problem_from_json(input) {
    Err(error) => {
      let payload : Json = { "ok": false, "error": error.message() }
      payload.stringify()
    }
    Ok(problem) =>
      match solve(problem) {
        Ok(result) => {
          let payload : Json = { "ok": true, "result": @json.to_json(result) }
          payload.stringify()
        }
        Err(error) => {
          let payload : Json = { "ok": false, "error": error.message() }
          payload.stringify()
        }
      }
  }
}

///|
pub fn demo_json() -> String {
  problem_to_json(demo_problem())
}

///|
pub fn sensitivity_json(input : String) -> String {
  try {
    let request : SensitivityRequest = @json.from_json(@json.parse(input))
    match sensitivity(request.problem, request.perturbation) {
      Ok(report) => {
        let payload : Json = { "ok": true, "result": @json.to_json(report) }
        payload.stringify()
      }
      Err(error) => {
        let payload : Json = { "ok": false, "error": error.message() }
        payload.stringify()
      }
    }
  } catch {
    _ => {
      let payload : Json = {
        "ok": false,
        "error": "invalid SensitivityRequest JSON",
      }
      payload.stringify()
    }
  }
}

///|
pub fn fixture_json(name : String) -> String {
  match named_problem(name) {
    Ok(problem) => problem_to_json(problem)
    Err(error) => {
      let payload : Json = { "ok": false, "error": error.message() }
      payload.stringify()
    }
  }
}

///|
pub fn inspect_json(input : String) -> String {
  match problem_from_json(input) {
    Err(error) => {
      let payload : Json = { "ok": false, "error": error.message() }
      payload.stringify()
    }
    Ok(problem) =>
      match validate_problem(problem) {
        Err(error) => {
          let payload : Json = { "ok": false, "error": error.message() }
          payload.stringify()
        }
        Ok(_) => {
          let payload : Json = {
            "ok": true,
            "elements": problem.elements.length(),
            "species": problem.species.length(),
            "minerals": problem.minerals.length(),
            "thermodynamics": @json.to_json(problem.thermodynamics),
          }
          payload.stringify()
        }
      }
  }
}