// Callback-free preflight and provenance sealing for the exact ordinary
// own-data receiver-call recipe. Once the registry is sealed, every managed
// activation revalidates the object, descriptor, callee, source, and realm.

///|
priv struct ReceiverRecursionPreflight {
  plan : ReceiverRecursionPlan
  throw_type_error_snapshot : CallbackFreeBindingSnapshot
  expected_global_object : ObjectData
  expected_object_prototype : Value
  expected_realm_protos : FunctionRealmProtos
  expected_source_identity : String?
}

///|
fn ReceiverRecursionPreflight::ReceiverRecursionPreflight(
  plan~ : ReceiverRecursionPlan,
  throw_type_error_snapshot~ : CallbackFreeBindingSnapshot,
  expected_global_object~ : ObjectData,
  expected_object_prototype~ : Value,
  expected_realm_protos~ : FunctionRealmProtos,
  expected_source_identity~ : String?,
) -> ReceiverRecursionPreflight {
  {
    plan,
    throw_type_error_snapshot,
    expected_global_object,
    expected_object_prototype,
    expected_realm_protos,
    expected_source_identity,
  }
}

///|
#warnings("-unused_field")
priv struct TrustedReceiverRecursionFunction {
  plan : ReceiverRecursionPlan
  receiver : Value
  callee : Value
  closure : Environment
  params : Array[String]
  body : Array[@ast.Stmt]
  source_text : String?
  expected_object_prototype : Value
  expected_realm_protos : FunctionRealmProtos
  expected_source_identity : String?
}

///|
fn TrustedReceiverRecursionFunction::TrustedReceiverRecursionFunction(
  plan~ : ReceiverRecursionPlan,
  receiver~ : Value,
  callee~ : Value,
  closure~ : Environment,
  params~ : Array[String],
  body~ : Array[@ast.Stmt],
  source_text~ : String?,
  expected_object_prototype~ : Value,
  expected_realm_protos~ : FunctionRealmProtos,
  expected_source_identity~ : String?,
) -> TrustedReceiverRecursionFunction {
  {
    plan,
    receiver,
    callee,
    closure,
    params,
    body,
    source_text,
    expected_object_prototype,
    expected_realm_protos,
    expected_source_identity,
  }
}

///|
#warnings("-unused_field")
priv struct TrustedReceiverRecursionRegistry {
  plan : ReceiverRecursionPlan
  trusted : TrustedReceiverRecursionFunction
  throw_type_error : Value?
  throw_type_error_snapshot : CallbackFreeBindingSnapshot
  expected_global_object : ObjectData
}

///|
fn TrustedReceiverRecursionRegistry::TrustedReceiverRecursionRegistry(
  plan~ : ReceiverRecursionPlan,
  trusted~ : TrustedReceiverRecursionFunction,
  throw_type_error~ : Value?,
  throw_type_error_snapshot~ : CallbackFreeBindingSnapshot,
  expected_global_object~ : ObjectData,
) -> TrustedReceiverRecursionRegistry {
  {
    plan,
    trusted,
    throw_type_error,
    throw_type_error_snapshot,
    expected_global_object,
  }
}

///|
fn receiver_recursion_value_identity(expected : Value, actual : Value) -> Bool {
  match (expected, actual) {
    (Object(expected_data), Object(actual_data)) =>
      physical_equal(expected_data, actual_data)
    _ => false
  }
}

///|
fn receiver_recursion_binding_value(
  interp : Interpreter,
  name : String,
  expected_kind : BindingKind,
) -> Value? {
  match interp.global.bindings.get(name) {
    Some(binding) if binding.initialized &&
      !binding.is_parameter &&
      binding.kind == expected_kind => Some(binding.value)
    _ => None
  }
}

///|
fn receiver_recursion_object_shape_matches(
  receiver : Value,
  callee : Value,
  expected_object_prototype : Value,
  plan : ReceiverRecursionPlan,
) -> Bool {
  match receiver {
    Object(data) => {
      guard data.class_name == "Object" &&
        data.callable is None &&
        same_value(data.prototype, expected_object_prototype) &&
        data.extensible &&
        data.arraybuffer_state is None &&
        data.bag.properties.length() == 2 &&
        data.bag.descriptors.is_empty() &&
        data.bag.symbol_properties.is_empty() &&
        data.bag.symbol_descriptors.is_empty() &&
        data.bag.internal_slots.is_empty() &&
        data.bag.host_slots.is_empty() else {
        return false
      }
      guard data.bag.properties.get(plan.marker_name) is Some(Number(marker)) &&
        marker == NUMERIC_RECURSION_BASE &&
        data.bag.properties.get(plan.property_name) is Some(actual_callee) &&
        receiver_recursion_value_identity(callee, actual_callee) else {
        return false
      }
      true
    }
    _ => false
  }
}

///|
fn receiver_recursion_user_func_matches_syntax(
  interp : Interpreter,
  trusted : TrustedReceiverRecursionFunction,
  data : FuncData,
) -> Bool {
  let plan = trusted.plan
  data.name == Some(plan.function_name) &&
  data.params.length() == 1 &&
  data.params[0] == plan.parameter &&
  data.body.length() == 2 &&
  physical_equal(data.closure, trusted.closure) &&
  physical_equal(data.closure, interp.global) &&
  !data.strict &&
  !data.has_name_binding &&
  !data.is_method &&
  data.source_text == trusted.source_text &&
  receiver_recursion_body_statement_matches(plan, 0, data.body[0]) &&
  receiver_recursion_body_statement_matches(plan, 1, data.body[1]) &&
  interp.global.parent is None
}

///|
fn receiver_recursion_function_identity_matches(
  expected : Value,
  actual : Value,
) -> Bool {
  receiver_recursion_value_identity(expected, actual)
}

///|
fn receiver_recursion_global_function_matches(
  interp : Interpreter,
  trusted : TrustedReceiverRecursionFunction,
  candidate : Value,
) -> Bool {
  guard candidate is Object(actual) &&
    actual.class_name == "Function" &&
    actual.callable is Some(UserFunc(data)) &&
    receiver_recursion_function_identity_matches(trusted.callee, candidate) &&
    physical_equal(data.params, trusted.params) &&
    physical_equal(data.body, trusted.body) &&
    interp.global.bindings.get(trusted.plan.function_name) is Some(binding) &&
    binding.initialized &&
    binding.kind == VarBinding &&
    binding.value is Object(bound) &&
    physical_equal(bound, actual) &&
    interp.global_this is Object(global_object) &&
    global_object.bag.properties.get(trusted.plan.function_name)
    is Some(Object(mirrored)) &&
    physical_equal(mirrored, actual) &&
    numeric_recursion_global_property_is_canonical(
      global_object,
      trusted.plan.function_name,
    ) &&
    receiver_recursion_user_func_matches_syntax(interp, trusted, data) else {
    return false
  }
  function_source_identity(candidate) == trusted.expected_source_identity &&
  numeric_recursion_realm_protos_match(
    trusted.expected_realm_protos,
    callee_realm_protos(candidate),
  )
}

///|
fn receiver_recursion_revalidate_trusted(
  interp : Interpreter,
  trusted : TrustedReceiverRecursionFunction,
  expected_global_object : ObjectData,
) -> Unit raise InvalidActivationDispatchShell {
  guard interp.numeric_recursion_canonical_global_object()
    is Some(actual_global_object) &&
    physical_equal(actual_global_object, expected_global_object) else {
    invalid_activation_dispatch_shell(
      "receiver recursion global changed after callback-free preflight",
    )
  }
  let receiver = match
    receiver_recursion_binding_value(
      interp,
      trusted.plan.receiver_name,
      LetBinding,
    ) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "receiver recursion object binding lost its trusted provenance",
      )
  }
  let callee = match
    receiver_recursion_binding_value(
      interp,
      trusted.plan.function_name,
      VarBinding,
    ) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "receiver recursion function binding lost its trusted provenance",
      )
  }
  guard receiver_recursion_value_identity(trusted.receiver, receiver) &&
    receiver_recursion_value_identity(trusted.callee, callee) &&
    receiver_recursion_object_shape_matches(
      receiver,
      callee,
      trusted.expected_object_prototype,
      trusted.plan,
    ) &&
    receiver_recursion_global_function_matches(interp, trusted, callee) else {
    invalid_activation_dispatch_shell(
      "receiver recursion object graph no longer matches sealed provenance",
    )
  }
}

///|
fn Interpreter::preflight_receiver_recursion_program(
  self : Interpreter,
  plan : ReceiverRecursionPlan,
) -> ReceiverRecursionPreflight? {
  guard self.numeric_recursion_canonical_global_object()
    is Some(expected_global_object) &&
    !self.global.bindings.contains(plan.receiver_name) &&
    !self.global.bindings.contains(plan.function_name) &&
    expected_global_object.bag.properties.get(plan.receiver_name) is None &&
    expected_global_object.bag.properties.get(plan.function_name) is None else {
    return None
  }
  let expected_realm_protos = function_realm_protos_from_realm_state(
    self.realm_state,
  )
  let expected_object_prototype = match
    getter_recursion_direct_object_prototype(self, expected_realm_protos) {
    Some(found) => found
    None => return None
  }
  let throw_type_error_snapshot = numeric_recursion_callback_free_throw_type_error(
    self.global,
  )
  match throw_type_error_snapshot {
    CallbackFreeBindingUnsafe => return None
    _ => ()
  }
  Some(
    ReceiverRecursionPreflight(
      plan~,
      throw_type_error_snapshot~,
      expected_global_object~,
      expected_object_prototype~,
      expected_realm_protos~,
      expected_source_identity=self.realm_state.active_source_identity.val,
    ),
  )
}

///|
#warnings("-unused_value")
fn Interpreter::preflight_dispatchable_receiver_recursion_program(
  self : Interpreter,
  stmts : Array[@ast.Stmt],
) -> ReceiverRecursionPreflight? {
  match classify_receiver_recursion_program(stmts) {
    Some(plan) if receiver_recursion_plan_is_dispatchable(plan) =>
      self.preflight_receiver_recursion_program(plan)
    _ => None
  }
}

///|
fn receiver_recursion_recipe_matches(
  expected : ReceiverRecursionExpressionRecipe,
  actual : ReceiverRecursionExpressionRecipe,
) -> Bool {
  expected.base_condition_loc == actual.base_condition_loc &&
  receiver_recursion_access_matches(
    expected.marker_property_access,
    actual.marker_property_access,
  ) &&
  expected.marker_member_loc == actual.marker_member_loc &&
  receiver_recursion_access_matches(
    expected.property_access,
    actual.property_access,
  ) &&
  receiver_recursion_access_matches(
    expected.marker_access,
    actual.marker_access,
  ) &&
  expected.recursive_add_loc == actual.recursive_add_loc &&
  receiver_recursion_access_matches(
    expected.recursive_access,
    actual.recursive_access,
  ) &&
  expected.recursive_member_loc == actual.recursive_member_loc &&
  expected.recursive_call_loc == actual.recursive_call_loc &&
  expected.recursive_subtract_loc == actual.recursive_subtract_loc &&
  receiver_recursion_access_matches(expected.root_access, actual.root_access) &&
  expected.root_member_loc == actual.root_member_loc
}

///|
fn receiver_recursion_plans_match(
  expected : ReceiverRecursionPlan,
  actual : ReceiverRecursionPlan,
) -> Bool {
  expected.function_name == actual.function_name &&
  expected.receiver_name == actual.receiver_name &&
  expected.parameter == actual.parameter &&
  expected.property_name == actual.property_name &&
  expected.marker_name == actual.marker_name &&
  expected.initial_argument == actual.initial_argument &&
  receiver_recursion_recipe_matches(expected.recipe, actual.recipe)
}

///|
fn receiver_recursion_trusted_function(
  interp : Interpreter,
  preflight : ReceiverRecursionPreflight,
) -> TrustedReceiverRecursionFunction raise InvalidActivationDispatchShell {
  let receiver = match
    receiver_recursion_binding_value(
      interp,
      preflight.plan.receiver_name,
      LetBinding,
    ) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "receiver recursion object binding was not initialized exactly once",
      )
  }
  let callee = match
    receiver_recursion_binding_value(
      interp,
      preflight.plan.function_name,
      VarBinding,
    ) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "receiver recursion function binding was not initialized exactly once",
      )
  }
  guard receiver is Object(_) &&
    callee is Object(callee_data) &&
    callee_data.class_name == "Function" &&
    callee_data.callable is Some(UserFunc(data)) else {
    invalid_activation_dispatch_shell(
      "receiver recursion root did not create an ordinary receiver and UserFunc",
    )
  }
  guard receiver_recursion_object_shape_matches(
    receiver,
    callee,
    preflight.expected_object_prototype,
    preflight.plan,
  ) else {
    invalid_activation_dispatch_shell(
      "receiver recursion root object graph is not the exact own-data recipe",
    )
  }
  let trusted = TrustedReceiverRecursionFunction(
    plan=preflight.plan,
    receiver~,
    callee~,
    closure=data.closure,
    params=data.params,
    body=data.body,
    source_text=data.source_text,
    expected_object_prototype=preflight.expected_object_prototype,
    expected_realm_protos=preflight.expected_realm_protos,
    expected_source_identity=preflight.expected_source_identity,
  )
  guard receiver_recursion_global_function_matches(interp, trusted, callee) else {
    invalid_activation_dispatch_shell(
      "receiver recursion root function no longer matches its admitted syntax",
    )
  }
  receiver_recursion_revalidate_trusted(
    interp,
    trusted,
    preflight.expected_global_object,
  )
  trusted
}

///|
#warnings("-unused_value")
fn Interpreter::seal_receiver_recursion_registry(
  self : Interpreter,
  preflight : ReceiverRecursionPreflight,
  stmts : Array[@ast.Stmt],
) -> TrustedReceiverRecursionRegistry raise InvalidActivationDispatchShell {
  guard self.numeric_recursion_canonical_global_object()
    is Some(actual_global_object) &&
    physical_equal(actual_global_object, preflight.expected_global_object) else {
    invalid_activation_dispatch_shell(
      "receiver recursion global changed during root setup",
    )
  }
  let actual_throw_type_error = numeric_recursion_callback_free_throw_type_error(
    self.global,
  )
  guard numeric_recursion_callback_free_binding_snapshots_match(
    preflight.throw_type_error_snapshot,
    actual_throw_type_error,
  ) else {
    invalid_activation_dispatch_shell(
      "receiver recursion ThrowTypeError binding changed during root setup",
    )
  }
  let actual_plan = match classify_receiver_recursion_program(stmts) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "receiver recursion program no longer satisfies exact admission",
      )
  }
  guard receiver_recursion_plans_match(preflight.plan, actual_plan) else {
    invalid_activation_dispatch_shell(
      "receiver recursion program no longer matches its preflight plan",
    )
  }
  let trusted = receiver_recursion_trusted_function(self, preflight)
  let registry = TrustedReceiverRecursionRegistry(
    plan=actual_plan,
    trusted~,
    throw_type_error=numeric_recursion_throw_type_error_value(
      actual_throw_type_error,
    ),
    throw_type_error_snapshot=preflight.throw_type_error_snapshot,
    expected_global_object=preflight.expected_global_object,
  )
  receiver_recursion_revalidate_trusted(
    self,
    registry.trusted,
    registry.expected_global_object,
  )
  registry
}

///|
fn TrustedReceiverRecursionRegistry::require_receiver(
  self : TrustedReceiverRecursionRegistry,
  interp : Interpreter,
) -> Value raise InvalidActivationDispatchShell {
  receiver_recursion_revalidate_trusted(
    interp,
    self.trusted,
    self.expected_global_object,
  )
  self.trusted.receiver
}

///|
fn TrustedReceiverRecursionRegistry::require_callee(
  self : TrustedReceiverRecursionRegistry,
  interp : Interpreter,
  candidate : Value,
) -> Value raise InvalidActivationDispatchShell {
  receiver_recursion_revalidate_trusted(
    interp,
    self.trusted,
    self.expected_global_object,
  )
  guard receiver_recursion_function_identity_matches(
    self.trusted.callee,
    candidate,
  ) else {
    invalid_activation_dispatch_shell(
      "receiver recursion callee identity is outside the sealed own data property",
    )
  }
  candidate
}

///|
#warnings("-unused_value")
fn TrustedReceiverRecursionRegistry::require_call(
  self : TrustedReceiverRecursionRegistry,
  interp : Interpreter,
  callee : Value,
  this_value : Value,
  args : Array[Value],
) -> Value raise InvalidActivationDispatchShell {
  let receiver = self.require_receiver(interp)
  let trusted_callee = self.require_callee(interp, callee)
  guard receiver_recursion_value_identity(receiver, this_value) &&
    args.length() == 1 &&
    args[0] is Number(_) else {
    invalid_activation_dispatch_shell(
      "receiver recursion call escaped the sealed receiver/argument envelope",
    )
  }
  trusted_callee
}