// Runtime provenance sealing for the exact public numeric call root. All
// reads are direct environment/object storage reads; no identifier lookup or
// guest callback is performed before admission has completed.

///|
priv struct DirectNumericRecursionFunctionSnapshot {
  syntax : NumericRecursionFunctionSyntax
  callee : Value
  params : Array[String]
  body : Array[@ast.Stmt]
  source_text : String?
  expected_realm_protos : FunctionRealmProtos
  expected_source_identity : String?
}

///|
fn DirectNumericRecursionFunctionSnapshot::DirectNumericRecursionFunctionSnapshot(
  syntax~ : NumericRecursionFunctionSyntax,
  callee~ : Value,
  params~ : Array[String],
  body~ : Array[@ast.Stmt],
  source_text~ : String?,
  expected_realm_protos~ : FunctionRealmProtos,
  expected_source_identity~ : String?,
) -> DirectNumericRecursionFunctionSnapshot {
  {
    syntax,
    callee,
    params,
    body,
    source_text,
    expected_realm_protos,
    expected_source_identity,
  }
}

///|
priv struct DirectNumericRecursionPreflight {
  plan : DirectNumericRecursionCallPlan
  entry : DirectNumericRecursionFunctionSnapshot
  peer : DirectNumericRecursionFunctionSnapshot?
  throw_type_error_snapshot : CallbackFreeBindingSnapshot
  expected_global_object : ObjectData
}

///|
fn DirectNumericRecursionPreflight::DirectNumericRecursionPreflight(
  plan~ : DirectNumericRecursionCallPlan,
  entry~ : DirectNumericRecursionFunctionSnapshot,
  peer~ : DirectNumericRecursionFunctionSnapshot?,
  throw_type_error_snapshot~ : CallbackFreeBindingSnapshot,
  expected_global_object~ : ObjectData,
) -> DirectNumericRecursionPreflight {
  { plan, entry, peer, throw_type_error_snapshot, expected_global_object }
}

///|
fn direct_numeric_recursion_recipe_matches(
  expected : NumericRecursionExpressionRecipe,
  actual : NumericRecursionExpressionRecipe,
) -> Bool {
  expected.base_condition_loc == actual.base_condition_loc &&
  expected.recursive_add_loc == actual.recursive_add_loc &&
  expected.recursive_call_loc == actual.recursive_call_loc &&
  expected.recursive_subtract_loc == actual.recursive_subtract_loc
}

///|
fn direct_numeric_recursion_syntax_matches(
  expected : NumericRecursionFunctionSyntax,
  actual : NumericRecursionFunctionSyntax,
) -> Bool {
  expected.name == actual.name &&
  expected.parameter == actual.parameter &&
  expected.recursive_callee == actual.recursive_callee &&
  expected.invocation_recipe == actual.invocation_recipe &&
  numeric_recursion_function_return_recipes_match(
    expected.return_recipe,
    actual.return_recipe,
  ) &&
  direct_numeric_recursion_recipe_matches(
    expected.expression_recipe,
    actual.expression_recipe,
  )
}

///|
fn numeric_recursion_apply_recipe_is_intrinsic(
  recipe : NumericRecursionInvocationRecipe,
) -> Bool {
  match recipe {
    NumericRecursionDirectCall => false
    NumericRecursionIntrinsicApplyArray
    | NumericRecursionIntrinsicApplyMappedArguments => true
  }
}

///|
fn Interpreter::numeric_recursion_apply_globals_are_canonical(
  self : Interpreter,
) -> Bool {
  guard self.global.bindings.get("undefined") is None ||
    self.global.bindings.get("undefined")
    is Some({ value: Undefined, kind: VarBinding, initialized: true, .. }) else {
    return false
  }
  guard self.global_this is Object(global_object) else { return false }
  guard global_object.bag.properties.get("undefined") is Some(Undefined) else {
    return false
  }
  let undefined_desc = match
    ordinary_get_own_string_desc(global_object.bag, "undefined") {
    Some(desc) => desc
    None => return false
  }
  guard !undefined_desc.is_accessor &&
    !undefined_desc.writable &&
    !undefined_desc.enumerable &&
    !undefined_desc.configurable else {
    return false
  }
  guard self.global.bindings.get("[[FunctionPrototype]]") is Some(binding) &&
    binding.initialized &&
    binding.value is Object(function_proto) else {
    return false
  }
  match
    (
      function_proto.bag.properties.get("apply"),
      ordinary_get_own_string_desc(function_proto.bag, "apply"),
    ) {
    (Some(Object(apply_data)), Some(apply_desc)) if !apply_desc.is_accessor &&
      apply_data.callable is Some(FuncApplyMethod(Undefined)) => true
    _ => false
  }
}

///|
fn Interpreter::numeric_recursion_apply_intrinsic_is_canonical(
  self : Interpreter,
  syntax : NumericRecursionFunctionSyntax,
  candidate : Value,
) -> Bool {
  guard numeric_recursion_apply_recipe_is_intrinsic(syntax.invocation_recipe) else {
    return true
  }
  guard self.numeric_recursion_apply_globals_are_canonical() else {
    return false
  }
  guard self.global.bindings.get("[[FunctionPrototype]]") is Some(binding) &&
    binding.initialized &&
    binding.value is Object(function_proto) else {
    return false
  }
  guard candidate is Object(candidate_data) &&
    candidate_data.prototype is Object(candidate_proto) &&
    physical_equal(candidate_proto, function_proto) &&
    ordinary_get_own_string_desc(candidate_data.bag, "apply") is None else {
    return false
  }
  match
    (
      function_proto.bag.properties.get("apply"),
      ordinary_get_own_string_desc(function_proto.bag, "apply"),
    ) {
    (Some(Object(apply_data)), Some(apply_desc)) if !apply_desc.is_accessor &&
      apply_data.callable is Some(FuncApplyMethod(Undefined)) => true
    _ => false
  }
}

///|
fn direct_numeric_recursion_cycles_match(
  expected : DirectNumericRecursionCycle,
  actual : DirectNumericRecursionCycle,
) -> Bool {
  match (expected, actual) {
    (DirectNumericSelf, DirectNumericSelf) => true
    (
      DirectNumericMutualPeer(expected_name),
      DirectNumericMutualPeer(actual_name),
    ) => expected_name == actual_name
    _ => false
  }
}

///|
fn direct_numeric_recursion_plans_match(
  expected : DirectNumericRecursionCallPlan,
  actual : DirectNumericRecursionCallPlan,
) -> Bool {
  direct_numeric_recursion_syntax_matches(expected.entry, actual.entry) &&
  direct_numeric_recursion_cycles_match(expected.cycle, actual.cycle) &&
  expected.initial_argument == actual.initial_argument &&
  expected.root_call_loc == actual.root_call_loc
}

///|
fn Interpreter::snapshot_direct_numeric_recursion_function(
  self : Interpreter,
  syntax : NumericRecursionFunctionSyntax,
  candidate : Value,
  expected_global_object : ObjectData,
  expected_realm_protos : FunctionRealmProtos,
  expected_source_identity : String?,
) -> DirectNumericRecursionFunctionSnapshot? {
  guard self.numeric_recursion_canonical_global_object() is Some(global_object) &&
    physical_equal(global_object, expected_global_object) else {
    return None
  }
  guard candidate is Object(actual) &&
    actual.class_name == "Function" &&
    self.global.bindings.get(syntax.name) is Some(binding) &&
    binding.initialized &&
    binding.kind == VarBinding &&
    binding.value is Object(bound) &&
    physical_equal(bound, actual) else {
    return None
  }
  guard self.numeric_recursion_apply_intrinsic_is_canonical(syntax, candidate) else {
    return None
  }
  guard global_object.bag.properties.get(syntax.name) is Some(Object(mirrored)) &&
    physical_equal(mirrored, actual) &&
    numeric_recursion_global_property_is_canonical(global_object, syntax.name) else {
    return None
  }
  guard actual.callable is Some(UserFunc(data)) &&
    numeric_recursion_user_func_matches_syntax(self, syntax, data) else {
    return None
  }
  guard function_source_identity(candidate) == expected_source_identity &&
    numeric_recursion_realm_protos_match(
      expected_realm_protos,
      callee_realm_protos(candidate),
    ) else {
    return None
  }
  Some(
    DirectNumericRecursionFunctionSnapshot(
      syntax~,
      callee=candidate,
      params=data.params,
      body=data.body,
      source_text=data.source_text,
      expected_realm_protos~,
      expected_source_identity~,
    ),
  )
}

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

///|
#warnings("-unused_value")
fn Interpreter::preflight_direct_numeric_recursion_call(
  self : Interpreter,
  callee : Value,
  this_value : Value,
  args : Array[Value],
  root_call_loc : @token.Loc,
) -> DirectNumericRecursionPreflight? {
  guard classify_direct_numeric_recursion_call(
      callee, this_value, args, root_call_loc,
    )
    is Some(plan) else {
    return None
  }
  guard self.numeric_recursion_canonical_global_object()
    is Some(expected_global_object) else {
    return None
  }
  let throw_type_error_snapshot = numeric_recursion_callback_free_throw_type_error(
    self.global,
  )
  match throw_type_error_snapshot {
    CallbackFreeBindingUnsafe => return None
    _ => ()
  }
  let entry_realm_protos = callee_realm_protos(callee)
  let entry_source_identity = function_source_identity(callee)
  guard self.snapshot_direct_numeric_recursion_function(
      plan.entry,
      callee,
      expected_global_object,
      entry_realm_protos,
      entry_source_identity,
    )
    is Some(entry) else {
    return None
  }
  let peer = match plan.cycle {
    DirectNumericSelf => None
    DirectNumericMutualPeer(peer_name) => {
      guard peer_name != plan.entry.name &&
        plan.entry.parameter != peer_name &&
        direct_numeric_recursion_global_candidate(self, peer_name)
        is Some(peer_candidate) &&
        peer_candidate is Object(peer_object) &&
        peer_object.callable is Some(UserFunc(peer_data)) &&
        classify_direct_numeric_recursion_user_func(peer_data)
        is Some((peer_syntax, peer_cycle)) else {
        return None
      }
      guard peer_syntax.name == peer_name &&
        peer_syntax.parameter != plan.entry.name &&
        peer_syntax.recursive_callee == plan.entry.name &&
        peer_cycle is DirectNumericMutualPeer(reverse_name) &&
        reverse_name == plan.entry.name else {
        return None
      }
      let peer_realm_protos = callee_realm_protos(peer_candidate)
      let peer_source_identity = function_source_identity(peer_candidate)
      guard self.snapshot_direct_numeric_recursion_function(
          peer_syntax, peer_candidate, expected_global_object, peer_realm_protos,
          peer_source_identity,
        )
        is Some(peer_snapshot) else {
        return None
      }
      guard peer_candidate is Object(peer_identity) &&
        callee is Object(entry_identity) &&
        !physical_equal(peer_identity, entry_identity) else {
        return None
      }
      Some(peer_snapshot)
    }
  }
  Some(
    DirectNumericRecursionPreflight(
      plan~,
      entry~,
      peer~,
      throw_type_error_snapshot~,
      expected_global_object~,
    ),
  )
}

///|
fn Interpreter::seal_direct_numeric_recursion_function(
  self : Interpreter,
  snapshot : DirectNumericRecursionFunctionSnapshot,
  expected_global_object : ObjectData,
) -> TrustedNumericRecursionFunction raise InvalidActivationDispatchShell {
  let actual = match
    self.snapshot_direct_numeric_recursion_function(
      snapshot.syntax,
      snapshot.callee,
      expected_global_object,
      snapshot.expected_realm_protos,
      snapshot.expected_source_identity,
    ) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "direct numeric recursion function provenance changed before sealing",
      )
  }
  guard physical_equal(snapshot.params, actual.params) &&
    physical_equal(snapshot.body, actual.body) &&
    snapshot.source_text == actual.source_text &&
    direct_numeric_recursion_syntax_matches(snapshot.syntax, actual.syntax) else {
    invalid_activation_dispatch_shell(
      "direct numeric recursion function recipe changed before sealing",
    )
  }
  TrustedNumericRecursionFunction(
    syntax=actual.syntax,
    callee=actual.callee,
    params=actual.params,
    body=actual.body,
    source_text=actual.source_text,
    expected_realm_protos=actual.expected_realm_protos,
    expected_source_identity=actual.expected_source_identity,
  )
}

///|
#warnings("-unused_value")
fn Interpreter::seal_direct_numeric_recursion_registry(
  self : Interpreter,
  preflight : DirectNumericRecursionPreflight,
  callee : Value,
  this_value : Value,
  args : Array[Value],
  root_call_loc : @token.Loc,
) -> TrustedNumericRecursionRegistry raise InvalidActivationDispatchShell {
  guard self.numeric_recursion_canonical_global_object() is Some(global_object) &&
    physical_equal(global_object, preflight.expected_global_object) else {
    invalid_activation_dispatch_shell(
      "direct numeric recursion global changed before sealing",
    )
  }
  guard classify_direct_numeric_recursion_call(
      callee, this_value, args, root_call_loc,
    )
    is Some(sealed_plan) &&
    direct_numeric_recursion_plans_match(preflight.plan, sealed_plan) else {
    invalid_activation_dispatch_shell(
      "direct numeric recursion call no longer matches exact admission",
    )
  }
  let throw_type_error_snapshot = numeric_recursion_callback_free_throw_type_error(
    self.global,
  )
  guard numeric_recursion_callback_free_binding_snapshots_match(
    preflight.throw_type_error_snapshot,
    throw_type_error_snapshot,
  ) else {
    invalid_activation_dispatch_shell(
      "direct numeric recursion ThrowTypeError binding changed before sealing",
    )
  }
  let entry = self.seal_direct_numeric_recursion_function(
    preflight.entry,
    preflight.expected_global_object,
  )
  let peer = match preflight.peer {
    Some(snapshot) =>
      Some(
        self.seal_direct_numeric_recursion_function(
          snapshot,
          preflight.expected_global_object,
        ),
      )
    None => None
  }
  TrustedNumericRecursionRegistry(
    entry~,
    peer~,
    throw_type_error=numeric_recursion_throw_type_error_value(
      throw_type_error_snapshot,
    ),
    throw_type_error_snapshot~,
    expected_global_object=preflight.expected_global_object,
  )
}