// Callback-free preflight and provenance sealing for the exact ordinary
// getter recursion slice. Root declarations run once before sealing; managed
// execution never falls back after the registry has been created.

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

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

///|
priv struct TrustedGetterRecursionFunction {
  plan : GetterRecursionPlan
  target : Value
  holder : Value
  getter : Value
  closure : Environment
  expected_source_text : String?
  expected_object_prototype : Value
  expected_realm_protos : FunctionRealmProtos
  expected_source_identity : String?
}

///|
fn TrustedGetterRecursionFunction::TrustedGetterRecursionFunction(
  plan~ : GetterRecursionPlan,
  target~ : Value,
  holder~ : Value,
  getter~ : Value,
  closure~ : Environment,
  expected_source_text~ : String?,
  expected_object_prototype~ : Value,
  expected_realm_protos~ : FunctionRealmProtos,
  expected_source_identity~ : String?,
) -> TrustedGetterRecursionFunction {
  {
    plan,
    target,
    holder,
    getter,
    closure,
    expected_source_text,
    expected_object_prototype,
    expected_realm_protos,
    expected_source_identity,
  }
}

///|
#warnings("-unused_field")
priv struct TrustedGetterRecursionRegistry {
  plan : GetterRecursionPlan
  trusted : TrustedGetterRecursionFunction
  throw_type_error : Value?
  expected_global_object : ObjectData
}

///|
fn TrustedGetterRecursionRegistry::TrustedGetterRecursionRegistry(
  plan~ : GetterRecursionPlan,
  trusted~ : TrustedGetterRecursionFunction,
  throw_type_error~ : Value?,
  expected_global_object~ : ObjectData,
) -> TrustedGetterRecursionRegistry {
  { plan, trusted, throw_type_error, expected_global_object }
}

///|
fn getter_recursion_direct_object_prototype(
  interp : Interpreter,
  realm_protos : FunctionRealmProtos,
) -> Value? {
  match
    (
      interp.global.bindings.get("[[ObjectPrototype]]"),
      realm_protos.object_proto,
    ) {
    (None, None) => Some(Null)
    (Some(binding), Some(realm_object_prototype)) if binding.initialized &&
      binding.value is Object(_) &&
      same_value(binding.value, realm_object_prototype) => Some(binding.value)
    _ => None
  }
}

///|
#warnings("-unused_value")
fn Interpreter::preflight_getter_recursion_program(
  self : Interpreter,
  plan : GetterRecursionPlan,
) -> GetterRecursionPreflight? {
  guard self.numeric_recursion_canonical_global_object()
    is Some(expected_global_object) &&
    !self.global.bindings.contains(plan.counter_name) &&
    !self.global.bindings.contains(plan.object_name) &&
    (
      getter_recursion_accessor_holder_name(plan) == plan.object_name ||
      !self.global.bindings.contains(
        getter_recursion_accessor_holder_name(plan),
      )
    ) 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(
    GetterRecursionPreflight(
      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_getter_recursion_program(
  self : Interpreter,
  stmts : Array[@ast.Stmt],
) -> GetterRecursionPreflight? {
  match classify_getter_recursion_program(stmts) {
    Some(plan) if getter_recursion_plan_is_dispatchable(plan) =>
      self.preflight_getter_recursion_program(plan)
    Some(_) | None => None
  }
}

///|
fn getter_recursion_plans_match(
  expected : GetterRecursionPlan,
  actual : GetterRecursionPlan,
) -> Bool {
  expected.counter_name == actual.counter_name &&
  expected.object_name == actual.object_name &&
  expected.property_name == actual.property_name &&
  same_value(Number(expected.initial_count), Number(actual.initial_count)) &&
  (match (expected.placement, actual.placement) {
    (GetterOwnAccessor, GetterOwnAccessor) => true
    (
      GetterDirectPrototypeAccessor(expected_holder),
      GetterDirectPrototypeAccessor(actual_holder),
    ) => expected_holder == actual_holder
    _ => false
  })
}

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

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

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

///|
fn getter_recursion_descriptor_matches(
  descriptor : PropDescriptor,
  getter : Value,
) -> Bool {
  !descriptor.writable &&
  descriptor.enumerable &&
  descriptor.configurable &&
  descriptor.is_accessor &&
  descriptor.setter is None &&
  (match descriptor.getter {
    Some(actual) => getter_recursion_getter_identity_matches(getter, actual)
    None => false
  })
}

///|
fn getter_recursion_target_and_holder_match(
  trusted : TrustedGetterRecursionFunction,
  target : Value,
  holder : Value,
) -> Bool {
  guard getter_recursion_target_identity_matches(trusted.target, target) &&
    getter_recursion_target_identity_matches(trusted.holder, holder) &&
    target is Object(target_data) &&
    target_data.class_name == "Object" &&
    target_data.callable is None &&
    holder is Object(holder_data) &&
    holder_data.class_name == "Object" &&
    holder_data.callable is None &&
    same_value(holder_data.prototype, trusted.expected_object_prototype) &&
    holder_data.bag.properties.get(trusted.plan.property_name)
    is Some(Undefined) &&
    holder_data.bag.descriptors.get(trusted.plan.property_name)
    is Some(descriptor) &&
    getter_recursion_descriptor_matches(descriptor, trusted.getter) else {
    return false
  }
  match trusted.plan.placement {
    GetterOwnAccessor =>
      getter_recursion_target_identity_matches(target, holder)
    GetterDirectPrototypeAccessor(_) =>
      !getter_recursion_target_identity_matches(target, holder) &&
      getter_recursion_target_identity_matches(holder, target_data.prototype) &&
      target_data.bag.properties.get(trusted.plan.property_name) is None &&
      target_data.bag.descriptors.get(trusted.plan.property_name) is None
  }
}

///|
fn getter_recursion_user_func_matches_syntax(
  interp : Interpreter,
  trusted : TrustedGetterRecursionFunction,
  data : FuncData,
) -> Bool {
  let plan = trusted.plan
  guard data.name == Some(plan.property_name) &&
    data.params.is_empty() &&
    data.body.length() == 3 &&
    !data.strict &&
    !data.has_name_binding &&
    data.is_method &&
    physical_equal(data.closure, trusted.closure) &&
    data.source_text == trusted.expected_source_text &&
    data.closure.parent is Some(parent) &&
    physical_equal(parent, interp.global) &&
    data.closure.with_object is None &&
    !data.closure.is_var_scope &&
    data.closure.bindings.get("[[HomeObject]]") is Some(home_binding) &&
    home_binding.initialized &&
    getter_recursion_target_identity_matches(trusted.holder, home_binding.value) else {
    return false
  }
  classify_getter_recursion_body(
    data.body,
    plan.counter_name,
    plan.object_name,
    plan.property_name,
    plan.recipe.root_member_loc,
  )
  is Some(_)
}

///|
fn getter_recursion_revalidate_trusted(
  interp : Interpreter,
  trusted : TrustedGetterRecursionFunction,
  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(
      "getter recursion global changed after callback-free preflight",
    )
  }
  let target = match
    getter_recursion_binding_value(interp, trusted.plan.object_name) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "getter recursion object binding lost its trusted provenance",
      )
  }
  let holder = match
    getter_recursion_binding_value(
      interp,
      getter_recursion_accessor_holder_name(trusted.plan),
    ) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "getter recursion accessor holder lost its runtime provenance",
      )
  }
  guard getter_recursion_target_and_holder_match(trusted, target, holder) else {
    invalid_activation_dispatch_shell(
      "getter recursion target and accessor holder no longer match the sealed relation",
    )
  }
  guard trusted.getter is Object(getter_data) &&
    getter_data.class_name == "Function" &&
    getter_data.callable is Some(UserFunc(data)) &&
    getter_recursion_user_func_matches_syntax(interp, trusted, data) else {
    invalid_activation_dispatch_shell(
      "getter recursion candidate no longer has exact UserFunc provenance",
    )
  }
  guard function_source_identity(trusted.getter) ==
    trusted.expected_source_identity else {
    invalid_activation_dispatch_shell(
      "getter recursion candidate source identity changed after sealing",
    )
  }
  guard numeric_recursion_realm_protos_match(
    trusted.expected_realm_protos,
    callee_realm_protos(trusted.getter),
  ) else {
    invalid_activation_dispatch_shell(
      "getter recursion candidate realm provenance changed after sealing",
    )
  }
}

///|
fn getter_recursion_trusted_function(
  interp : Interpreter,
  preflight : GetterRecursionPreflight,
) -> TrustedGetterRecursionFunction raise InvalidActivationDispatchShell {
  let target = match
    getter_recursion_binding_value(interp, preflight.plan.object_name) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "getter recursion object binding was not initialized exactly once",
      )
  }
  let holder = match
    getter_recursion_binding_value(
      interp,
      getter_recursion_accessor_holder_name(preflight.plan),
    ) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "getter recursion accessor holder binding was not initialized exactly once",
      )
  }
  guard getter_recursion_binding_value(interp, preflight.plan.counter_name)
    is Some(Number(count)) &&
    same_value(Number(count), Number(preflight.plan.initial_count)) else {
    invalid_activation_dispatch_shell(
      "getter recursion counter binding was not initialized exactly once",
    )
  }
  guard holder is Object(holder_data) &&
    holder_data.bag.properties.get(preflight.plan.property_name)
    is Some(Undefined) &&
    holder_data.bag.descriptors.get(preflight.plan.property_name)
    is Some(descriptor) &&
    descriptor.getter is Some(getter) else {
    invalid_activation_dispatch_shell(
      "getter recursion root did not create its exact accessor holder",
    )
  }
  guard getter is Object(getter_data) &&
    getter_data.class_name == "Function" &&
    getter_data.callable is Some(UserFunc(data)) else {
    invalid_activation_dispatch_shell(
      "getter recursion root did not create a UserFunc getter",
    )
  }
  let trusted = TrustedGetterRecursionFunction(
    plan=preflight.plan,
    target~,
    holder~,
    getter~,
    closure=data.closure,
    expected_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 getter_recursion_target_and_holder_match(trusted, target, holder) &&
    getter_recursion_descriptor_matches(descriptor, getter) &&
    getter_recursion_user_func_matches_syntax(interp, trusted, data) else {
    invalid_activation_dispatch_shell(
      "getter recursion root getter no longer matches its admitted syntax",
    )
  }
  guard function_source_identity(getter) == preflight.expected_source_identity &&
    numeric_recursion_realm_protos_match(
      preflight.expected_realm_protos,
      callee_realm_protos(getter),
    ) else {
    invalid_activation_dispatch_shell(
      "getter recursion UserFunc realm or source provenance changed during root setup",
    )
  }
  trusted
}

///|
#warnings("-unused_value")
fn Interpreter::seal_getter_recursion_registry(
  self : Interpreter,
  preflight : GetterRecursionPreflight,
  stmts : Array[@ast.Stmt],
) -> TrustedGetterRecursionRegistry 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(
      "getter recursion global changed after callback-free preflight",
    )
  }
  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(
      "getter recursion ThrowTypeError binding changed after callback-free preflight",
    )
  }
  let actual_plan = match classify_getter_recursion_program(stmts) {
    Some(found) => found
    None =>
      invalid_activation_dispatch_shell(
        "getter recursion program no longer satisfies exact admission",
      )
  }
  guard getter_recursion_plans_match(preflight.plan, actual_plan) else {
    invalid_activation_dispatch_shell(
      "getter recursion program no longer matches its preflight plan",
    )
  }
  let trusted = getter_recursion_trusted_function(self, preflight)
  let registry = TrustedGetterRecursionRegistry(
    plan=actual_plan,
    trusted~,
    throw_type_error=numeric_recursion_throw_type_error_value(
      actual_throw_type_error,
    ),
    expected_global_object=preflight.expected_global_object,
  )
  getter_recursion_revalidate_trusted(
    self,
    registry.trusted,
    registry.expected_global_object,
  )
  registry
}

///|
#warnings("-unused_value")
fn TrustedGetterRecursionRegistry::require_target(
  self : TrustedGetterRecursionRegistry,
  interp : Interpreter,
) -> Value raise InvalidActivationDispatchShell {
  getter_recursion_revalidate_trusted(
    interp,
    self.trusted,
    self.expected_global_object,
  )
  self.trusted.target
}

///|
#warnings("-unused_value")
fn TrustedGetterRecursionRegistry::require_getter(
  self : TrustedGetterRecursionRegistry,
  interp : Interpreter,
  candidate : Value,
  receiver : Value,
) -> GetterRecursionPlan raise InvalidActivationDispatchShell {
  let target = self.require_target(interp)
  guard getter_recursion_target_identity_matches(target, receiver) else {
    invalid_activation_dispatch_shell(
      "getter recursion receiver is outside the trusted target",
    )
  }
  guard getter_recursion_getter_identity_matches(self.trusted.getter, candidate) else {
    invalid_activation_dispatch_shell(
      "getter recursion callee identity is outside the trusted accessor",
    )
  }
  self.plan
}