///|
/// Frozen correlation and identities expected by this host operation.
pub(all) struct Expected {
  invocation : String
  project_scope : String
  release : String
  policy_sha256 : String
  lock_sha256 : String
}

///|
/// Encode an effect-free one-shot capability probe.
pub fn capabilities_request(
  invocation : String,
  project_scope : String,
) -> Bytes raise {
  if invocation.length() > MAX_REQUEST_BYTES ||
    project_scope.length() > MAX_REQUEST_BYTES {
    raise Invalid("metadata.restore_request_limit")
  }
  let bytes = @binary.write_binary_value(
    record([
      ("invocation", @ion.IonValue::string(invocation)),
      ("projectScope", @ion.IonValue::string(project_scope)),
    ]).with_annotation(@ion.symbol("morphir_provider_capabilities_request_v1")),
  )
  if bytes.length() > MAX_REQUEST_BYTES {
    raise Invalid("metadata.restore_request_limit")
  }
  bytes
}

///|
/// Encode granted roots and immutable policy/lock snapshots as binary Ion blobs.
/// Caller must qualify the selected service and grant these paths independently.
pub fn snapshot_request(
  expected : Expected,
  policy : Bytes,
  lock : Bytes,
  registry : String,
  state : String,
  output : String,
) -> Bytes raise {
  if @identity.sha256(policy) != expected.policy_sha256 ||
    @identity.sha256(lock) != expected.lock_sha256 {
    raise Invalid("metadata.restore_request_identity_mismatch")
  }
  let bytes = @binary.write_binary_value(
    record([
      ("invocation", @ion.IonValue::string(expected.invocation)),
      ("projectScope", @ion.IonValue::string(expected.project_scope)),
      ("release", @ion.IonValue::string(expected.release)),
      ("assurance", @ion.IonValue::string("portable")),
      ("registry", @ion.IonValue::string(registry)),
      ("state", @ion.IonValue::string(state)),
      ("output", @ion.IonValue::string(output)),
      ("policy", @ion.IonValue::blob(policy)),
      ("lock", @ion.IonValue::blob(lock)),
    ]).with_annotation(@ion.symbol("morphir_provider_snapshot_request_v1")),
  )
  if bytes.length() > MAX_REQUEST_BYTES {
    raise Invalid("metadata.restore_request_limit")
  }
  bytes
}

///|
fn correlate(
  values : Map[String, @ion.IonValue],
  invocation : String,
  project_scope : String,
) -> Unit raise {
  if text(required(values, "invocation")) != invocation ||
    text(required(values, "projectScope")) != project_scope ||
    text(required(values, "service")) != SERVICE_ID ||
    text(required(values, "profile")) != "local-library-mvp" ||
    text(required(values, "profileVersion")) != "0.1.0-draft.1" ||
    text(required(values, "codec")) != "v4.1-document-metadata-to-v4.0/1" {
    invalid("Correlation, service or execution profile mismatch")
  }
}

///|
/// Check this exact protocol revision. A response is not executable attestation.
pub fn admit_capabilities(
  bytes : Bytes,
  invocation : String,
  project_scope : String,
) -> Unit raise {
  let values = frame(bytes, "morphir_provider_capabilities_response_v1", [
    "invocation", "projectScope", "service", "profile", "profileVersion", "codec",
    "maxRequestBytes", "maxResponseBytes", "maxLibraries", "maxContexts", "maxTotalContexts",
  ])
  correlate(values, invocation, project_scope)
  for
    (key, expected) in [
      ("maxRequestBytes", MAX_REQUEST_BYTES),
      ("maxResponseBytes", MAX_RESPONSE_BYTES),
      ("maxLibraries", 128),
      ("maxContexts", 256),
      ("maxTotalContexts", 1024),
    ] {
    let value = required(values, key)
    if value.has_annotations() || !value.equals(@ion.IonValue::int(expected)) {
      invalid("Unsupported framing limit")
    }
  }
}