///|
/// Conversion is local to this JSON provider. The engine retains Ion snapshots.
pub fn rust_classic_ir(unit : @data.IRUnit) -> @classic.IRFile raise {
  if !unit.metadata.is_empty() ||
    !unit.provenance.is_empty() ||
    unit.annotations.any(a => a.text() != Some("morphir_pipeline")) {
    raise @protocol.Invalid("conformance.rust_envelope_metadata")
  }
  let old = @migration.migrate(
    @versioned.V4(unit.distribution),
    @version.FormatVersion::baseline(3U),
  )
  guard old is V3(classic) else { abort("classic conversion") }
  if @migration.to_current(@versioned_json.read(@versioned_json.write(old))) !=
    unit.distribution {
    raise @protocol.Invalid("conformance.rust_lossy_ir_conversion")
  }
  classic
}

///|
/// The established intersection is deliberately narrower than the upstream
/// draft. All definitions are checked, since its preflight scans the whole model.
fn rust_supported_ir(file : @classic.IRFile) -> Unit raise {
  guard file.distribution is Library(_, _, definition) else {
    raise @protocol.Invalid("conformance.rust_unsupported_distribution")
  }
  for _, module_value in definition.modules {
    for _, value in module_value.value.values {
      match value.value.value.body {
        Literal(_, BoolLiteral(_)) | Variable(_, _) | Unit(_) => ()
        Literal(_, StringLiteral(s)) =>
          ignore(@sdk.Text::from_string(s).to_string())
        Literal(_, WholeNumberLiteral(n)) =>
          ignore(
            rust_wire_value(
              @protocol.Value::from_json({ "type": "int", "value": n }),
            ),
          )
        _ => raise @protocol.Invalid("conformance.rust_unsupported_expression")
      }
    }
  }
}

///|
fn rust_definition(
  file : @classic.IRFile,
  entry : String,
) -> @classic.ValueDefinition[Unit, @classic.Type[Unit]] raise {
  let name = @ir.FQName::parse(entry)
  guard file.distribution is Library(package_name, _, definition) &&
    package_name == name.package_name else {
    raise @protocol.Invalid("conformance.rust_entry")
  }
  guard definition.modules.get(name.module_name) is Some(module_definition) else {
    raise @protocol.Invalid("conformance.rust_entry")
  }
  guard module_definition.value.values.get(name.local_name) is Some(value) else {
    raise @protocol.Invalid("conformance.rust_entry")
  }
  value.value.value
}

///|
fn rust_wire_value(value : @protocol.Value) -> Json raise {
  match value {
    Integer(n) => {
      if n.to_string() != "0" &&
        (
          n.compare_int64(-9223372036854775808L) < 0 ||
          n.compare_int64(9223372036854775807L) > 0
        ) {
        raise @protocol.Invalid("conformance.rust_integer_range")
      }
      { "kind": "integer", "value": n.to_string() }
    }
    Boolean(b) => { "kind": "boolean", "value": b }
    Unit => { "kind": "unit" }
    Text(units) =>
      {
        "kind": "string",
        "value": @sdk.Text::from_units(units.map(n => n.to_uint16())).to_string(),
      }
    _ => raise @protocol.Invalid("conformance.rust_unsupported_value")
  }
}

///|
fn rust_supported_type(value : @protocol.ValueType) -> Bool {
  value is Int || value is Bool || value is Unit || value is Text
}

///|
fn rust_result(value : Json) -> @protocol.Value raise {
  match @wire.string(@wire.field(value, "kind")) {
    "integer" =>
      @protocol.Value::from_json({
        "type": "int",
        "value": @wire.string(@wire.field(value, "value")),
      })
    "boolean" =>
      @protocol.Value::from_json({
        "type": "bool",
        "value": @wire.field(value, "value"),
      })
    "unit" => @protocol.Unit
    "string" =>
      @protocol.Text(
        @sdk.Text::from_string(@wire.string(@wire.field(value, "value")))
        .to_units()
        .map(n => n.to_int()),
      )
    _ => raise @protocol.Invalid("conformance.rust_unsupported_result")
  }
}

///|
fn rust_binary_identity(
  program : String,
  helper : String,
  node : String,
) -> String raise {
  let result = @platform.process(
    node,
    [helper, "--identity", program],
    @platform.cwd(),
    "",
    5000,
  )
  if build_field(result, "exitCode") != Json::number(0.0) {
    raise @protocol.Invalid(
      "conformance.rust_identity_unavailable: " + build_text(result, "stderr"),
    )
  }
  @wire.string(
    @wire.field(@wire.parse(build_text(result, "stdout")), "binaryIdentity"),
  )
}

///|
pub fn rust_evaluator_lane(
  program : String,
  helper : String,
  pin : Json?,
  node~ : String = "node",
  required~ : Bool = false,
  timeout~ : Int32 = 5000,
  cancel_file~ : String = "",
) -> @conformance.EvaluatorLane raise {
  let unavailable : @conformance.EvaluatorLane = {
    name: "rust",
    required,
    evaluator: None,
    unsupported: fn(_, _) { None },
    evidence: { "protocol": "1.1.0-draft.1", "reason": "missing_binary_or_pin" },
  }
  if program == "" || pin is None {
    return unavailable
  }
  if !@platform.process_supported() ||
    @platform.kind(program) != 1 ||
    helper == "" ||
    @platform.kind(helper) != 1 {
    return {
      ..unavailable,
      evidence: {
        "protocol": "1.1.0-draft.1",
        "reason": "provider_unavailable",
      },
    }
  }
  let pin = pin.unwrap()
  if @wire.string(@wire.field(pin, "profile")) !=
    "morphir-rust-evaluator-pin-v1" ||
    @wire.string(@wire.field(pin, "protocol")) != "1.1.0-draft.1" {
    raise @protocol.Invalid("conformance.rust_pin_profile")
  }
  let source = @wire.string(@wire.field(pin, "sourceRevision"))
  let runtime = @wire.string(@wire.field(pin, "runtimeRevision"))
  let identity = @wire.string(@wire.field(pin, "binaryIdentity"))
  fn hex(value : String, size : Int32) -> Bool {
    value.length() == size &&
    value.to_array().all(c => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
  }
  if !hex(source, 40) ||
    !hex(runtime, 40) ||
    !hex(identity, 64) ||
    timeout < 1 ||
    timeout > 300000 {
    raise @protocol.Invalid("conformance.rust_pin_identity")
  }
  if rust_binary_identity(program, helper, node) != identity {
    raise @protocol.Invalid("conformance.rust_binary_identity")
  }
  let helper_identity = @identity.sha256(@platform.read_bytes(helper))
  {
    name: "rust",
    required,
    evidence: {
      "sourceRevision": source,
      "runtimeRevision": runtime,
      "binaryIdentity": identity,
      "helperIdentity": helper_identity,
      "protocol": "1.1.0-draft.1",
      "conversion": "lossless-classic-v3-roundtrip",
      "supportedTypes": ["int64", "bool", "unit", "valid-utf16-text"],
      "supportedIR": "sdk-free scalar literal and identity definitions",
      "errorMappings": {
        "runtime": "conformance.rust_runtime_error",
        "codec": "unsupported",
        "unavailable": "unavailable",
      },
    },
    unsupported: fn(plan, call) {
      try {
        rust_supported_ir(rust_classic_ir(plan.unit))
        let entry = plan.library.invocations.filter(e => e.fqname == call.entry)[0]
        if !rust_supported_type(entry.output) ||
          !entry.inputs.all(rust_supported_type) {
          return Some("conformance.rust_unsupported_type")
        }
        for argument in call.arguments {
          ignore(rust_wire_value(argument))
        }
        None
      } catch {
        @wire.Invalid(code, detail) =>
          Some("conformance.rust_ir: " + code + ": " + detail)
        @protocol.Invalid(code) => Some(code)
        @sdk.UnrepresentableText(_) =>
          Some("conformance.rust_unrepresentable_utf16")
        _ => Some("conformance.rust_unsupported_conversion")
      }
    },
    evaluator: Some({
      profile: "rust-classic-v3/1.1.0-draft.1/int64-bounded",
      evaluate: fn(unit, call, output, definitions) raise {
        if rust_binary_identity(program, helper, node) != identity ||
          @identity.sha256(@platform.read_bytes(helper)) != helper_identity {
          raise @protocol.Invalid("conformance.rust_changed_binary_or_helper")
        }
        let file = rust_classic_ir(unit)
        rust_supported_ir(file)
        let definition = rust_definition(file, call.entry)
        if call.arguments.length() != definition.input_types.length() {
          raise @protocol.Invalid("conformance.rust_arity")
        }
        let arguments : Array[Json] = []
        for i, argument in call.arguments {
          arguments.push({
            "type": @classic_json.write_type(definition.input_types[i].2, fn(
              value,
            ) {
              @wire.write_unit(value)
            }),
            "value": rust_wire_value(argument),
          })
        }
        let request : Json = {
          "version": "1.1.0-draft.1",
          "provider": "morphir_ir",
          "program": {
            "kind": "morphir_ir",
            "distribution": @versioned_json.to_json(@versioned.V3(file)),
          },
          "calls": [
            { "id": call.id, "entrypoint": call.entry, "arguments": arguments },
          ],
          "limits": { "fuel": 100000, "maxCallDepth": 128 },
          "timeout_ms": timeout,
        }
        let bytes = @utf8.encode(request.stringify())
        if bytes.length() > 8388608 {
          raise @protocol.Invalid("conformance.rust_request_limit")
        }
        let directory = @platform.temporary()
        let path = directory + "/request.json"
        let mut retained = false
        let outcome = try {
          @platform.write(
            path,
            (
              {
                "profile": "morphir-rust-evaluator-worker-v1",
                "program": program,
                "binaryIdentity": identity,
                "helperIdentity": helper_identity,
                "input": request.stringify(),
                "timeout": timeout,
                "cancelFile": cancel_file,
              } : Json).stringify(),
          )
          let result = @platform.process(
            node,
            [helper, path],
            @platform.cwd(),
            "",
            timeout + 6000,
          )
          let diagnostic = build_text(result, "stderr")
          let code = build_field(result, "exitCode")
          if diagnostic.contains("execution.tree_termination_failed") ||
            diagnostic.contains("execution.tree_termination_unavailable") ||
            (match code {
              Number(n, ..) => n == -1.0 || n == 124.0 || n >= 128.0
              _ => true
            }) {
            retained = true
            raise @protocol.Invalid(
              "conformance.rust_provider_terminated: " +
              code.stringify() +
              ": " +
              diagnostic,
            )
          }
          if code != Json::number(0.0) {
            raise @protocol.Invalid(
              "conformance.rust_provider_failed: " + diagnostic,
            )
          }
          let response = @wire.parse(build_text(result, "stdout"))
          if @wire.string(@wire.field(response, "profile")) !=
            "morphir-rust-evaluator-result-v1" ||
            @wire.string(@wire.field(response, "binaryIdentity")) != identity ||
            @wire.string(@wire.field(response, "helperIdentity")) !=
            helper_identity ||
            @wire.string(@wire.field(response, "requestIdentity")) !=
            @identity.sha256(bytes) {
            raise @protocol.Invalid("conformance.rust_receipt_identity")
          }
          let report = @wire.field(response, "report")
          if @wire.string(@wire.field(report, "version")) != "1.1.0-draft.1" ||
            @wire.string(@wire.field(report, "provider")) != "morphir_ir" {
            raise @protocol.Invalid("conformance.rust_protocol")
          }
          let results = @wire.tuple(@wire.field(report, "results"), 1)
          let result = results[0]
          if @wire.string(@wire.field(result, "id")) != call.id ||
            @wire.string(@wire.field(result, "entrypoint")) != call.entry {
            raise @protocol.Invalid("conformance.rust_call_identity")
          }
          if @wire.string(@wire.field(result, "status")) == "error" {
            raise @protocol.Invalid(
              "conformance.rust_runtime_error: " +
              @wire.string(@wire.field(result, "code")),
            )
          }
          if @wire.string(@wire.field(result, "status")) != "value" {
            raise @protocol.Invalid("conformance.rust_status")
          }
          let value = @wire.field(result, "value")
          if @classic_json.read_type(@wire.field(value, "type"), fn(
              value,
            ) raise {
              @wire.unit(value)
            }) !=
            definition.output_type {
            raise @protocol.Invalid("conformance.rust_return_type")
          }
          let value = rust_result(@wire.field(value, "value"))
          ignore(rust_wire_value(value))
          @protocol.validate_as(value, output, definitions, output=true)
          if rust_binary_identity(program, helper, node) != identity ||
            @identity.sha256(@platform.read_bytes(helper)) != helper_identity {
            raise @protocol.Invalid("conformance.rust_changed_binary_or_helper")
          }
          Ok(value)
        } catch {
          error => Err(error)
        }
        let cleanup : Error? = if retained {
          Some(
            @protocol.Invalid(
              "conformance.rust_tree_cleanup_pending: " + directory,
            ),
          )
        } else {
          try {
            @platform.remove_dir(directory)
            None
          } catch {
            error => Some(error)
          }
        }
        match (outcome, cleanup) {
          (Ok(value), None) => value
          (Err(error), None) => raise error
          (result, Some(error)) =>
            raise @execution.CleanupFailed(
              match result {
                Err(primary) => Some(primary)
                _ => None
              },
              error,
            )
        }
      },
    }),
  }
}