///|
priv suberror IonInputError {
  IonInputError(String, String, Int32?)
} derive(Debug)

///|
fn parse_ion_input(
  bytes : Bytes,
  format : ValueInputFormat,
  index : Int32?,
) -> @ion.Ion raise IonInputError {
  if bytes.length() > 16 * 1024 * 1024 {
    raise IonInputError(
      "IonInputLimit",
      "I expected an Ion input of at most 16 MiB.",
      None,
    )
  }
  let values = try {
    let fuel = @ion.Limited(@ion.Budget(128))
    match format {
      IonTextInput =>
        @ion_text.read_ion_datagram(@ion_text.decode_ion_text(bytes), fuel~)
      IonBinaryInput => @ion_binary.read_binary(bytes, fuel~)
      JsonInput =>
        raise @ion.Unsupported("I expected an explicit Ion input format.")
    }
  } catch {
    @ion.FuelExhausted(location~, ..) as error => {
      let offset = match location {
        TextOffset(n) | BinaryOffset(n) | CharacterOffset(n) => Some(n)
        _ => None
      }
      raise IonInputError("IonInputLimit", error.message(), offset)
    }
    error => raise IonInputError("InvalidIon", error.message(), None)
  }
  match index {
    Some(index) => {
      guard index >= 0 && values.get(index) is Some(value) else {
        raise IonInputError(
          "IonSelection",
          "I expected --ion-index to select an existing user value in the Ion datagram.",
          None,
        )
      }
      value
    }
    None => {
      guard values is [value] else {
        raise IonInputError(
          "IonSelection",
          "I expected exactly one Ion user value; use --ion-index to select from a datagram.",
          None,
        )
      }
      value
    }
  }
}

///|
fn run_ion_validation(opts : ValueOptions, model : @smithy.Model) -> Int32 {
  let prepared = @ion_validation.PreparedValidator::prepare(
    model,
    opts.target,
    config=opts.config,
  ) catch {
    @validation.InvalidTarget(id, message) =>
      return value_problem(
        opts,
        "InvalidTarget",
        id.to_string() + ": " + message,
        2,
        source=Some("--shape"),
      )
    @validation.InvalidConfiguration(message) =>
      return value_problem(opts, "InvalidConfiguration", message, 2)
    @validation.InvalidModel(id, message) =>
      return value_problem(
        opts,
        "InvalidModel",
        id.to_string() + ": " + message,
        1,
        source=model.shapes.get(id).map(s => s.source.file),
      )
    @validation.UnsupportedConstraint(id, message) =>
      return value_problem(
        opts,
        "UnsupportedConstraint",
        id.to_string() + ": " + message,
        2,
        source=model.shapes.get(id).map(s => s.source.file),
      )
  }
  let bytes = read_ion_bytes(opts.file) catch {
    IonInputError(code, message, offset) =>
      return value_problem(opts, code, message, 1, offset~)
    _ =>
      return value_problem(
        opts, "InputRead", "I could not read the Ion value input.", 1,
      )
  }
  let value = parse_ion_input(bytes, opts.input_format, opts.ion_index) catch {
    IonInputError(code, message, offset) =>
      return value_problem(opts, code, message, 1, offset~)
  }
  let result = prepared.validate(value)
  if opts.diagnostics == JsonFindings {
    println(@json.to_json(result).stringify())
  } else {
    for finding in result.findings {
      eprintln(
        "\{opts.file}\{finding.path.pointer()}: error[\{finding.code.to_string()}] \{finding.shape}: \{finding.message}",
      )
    }
  }
  if result.is_valid() {
    0
  } else {
    1
  }
}