// Exact, callback-free admission for the first ordinary receiver-call slice.
// The syntax recipe is deliberately closed; runtime identity and descriptor
// provenance are sealed by activation_dispatch_receiver_trust.mbt.

///|
#warnings("-unused_value")
priv enum ReceiverRecursionAccessKind {
  ReceiverRecursionStatic
  ReceiverRecursionComputedLiteral(@token.Loc, Bool)
}

///|
#warnings("-unused_field")
priv struct ReceiverRecursionExpressionRecipe {
  base_condition_loc : @token.Loc
  marker_property_access : ReceiverRecursionAccessKind
  marker_member_loc : @token.Loc
  marker_access : ReceiverRecursionAccessKind
  recursive_add_loc : @token.Loc
  property_access : ReceiverRecursionAccessKind
  recursive_member_loc : @token.Loc
  recursive_access : ReceiverRecursionAccessKind
  recursive_call_loc : @token.Loc
  recursive_subtract_loc : @token.Loc
  root_access : ReceiverRecursionAccessKind
  root_member_loc : @token.Loc
}

///|
fn ReceiverRecursionExpressionRecipe::ReceiverRecursionExpressionRecipe(
  base_condition_loc~ : @token.Loc,
  marker_property_access~ : ReceiverRecursionAccessKind,
  marker_member_loc~ : @token.Loc,
  marker_access~ : ReceiverRecursionAccessKind,
  recursive_add_loc~ : @token.Loc,
  property_access~ : ReceiverRecursionAccessKind,
  recursive_member_loc~ : @token.Loc,
  recursive_access~ : ReceiverRecursionAccessKind,
  recursive_call_loc~ : @token.Loc,
  recursive_subtract_loc~ : @token.Loc,
  root_access~ : ReceiverRecursionAccessKind,
  root_member_loc~ : @token.Loc,
) -> ReceiverRecursionExpressionRecipe {
  {
    base_condition_loc,
    marker_property_access,
    marker_member_loc,
    marker_access,
    recursive_add_loc,
    property_access,
    recursive_member_loc,
    recursive_access,
    recursive_call_loc,
    recursive_subtract_loc,
    root_access,
    root_member_loc,
  }
}

///|
#warnings("-unused_field")
priv struct ReceiverRecursionPlan {
  function_name : String
  receiver_name : String
  parameter : String
  property_name : String
  marker_name : String
  initial_argument : Double
  recipe : ReceiverRecursionExpressionRecipe
}

///|
fn ReceiverRecursionPlan::ReceiverRecursionPlan(
  function_name~ : String,
  receiver_name~ : String,
  parameter~ : String,
  property_name~ : String,
  marker_name~ : String,
  initial_argument~ : Double,
  recipe~ : ReceiverRecursionExpressionRecipe,
) -> ReceiverRecursionPlan {
  {
    function_name,
    receiver_name,
    parameter,
    property_name,
    marker_name,
    initial_argument,
    recipe,
  }
}

///|
fn receiver_recursion_access_mode_matches(
  left : ReceiverRecursionAccessKind,
  right : ReceiverRecursionAccessKind,
) -> Bool {
  match (left, right) {
    (ReceiverRecursionStatic, ReceiverRecursionStatic) => true
    (
      ReceiverRecursionComputedLiteral(_, left_escaped),
      ReceiverRecursionComputedLiteral(_, right_escaped),
    ) => left_escaped == right_escaped
    _ => false
  }
}

///|
fn receiver_recursion_access_matches(
  expected : ReceiverRecursionAccessKind,
  actual : ReceiverRecursionAccessKind,
) -> Bool {
  match (expected, actual) {
    (ReceiverRecursionStatic, ReceiverRecursionStatic) => true
    (
      ReceiverRecursionComputedLiteral(expected_loc, expected_escaped),
      ReceiverRecursionComputedLiteral(actual_loc, actual_escaped),
    ) => expected_loc == actual_loc && expected_escaped == actual_escaped
    _ => false
  }
}

///|
fn receiver_recursion_property_access(
  property : @ast.Property,
) -> (String, ReceiverRecursionAccessKind)? {
  guard property.kind == @ast.Init && !property.is_method else { return None }
  match property.key {
    @ast.StringLit(name, has_escape, @token.LexForm::LexNormal, key_loc) if name !=
      "__proto__" =>
      if property.computed {
        Some((name, ReceiverRecursionComputedLiteral(key_loc, has_escape)))
      } else if !has_escape {
        Some((name, ReceiverRecursionStatic))
      } else {
        None
      }
    _ => None
  }
}

///|
fn receiver_recursion_this_member_access(
  expr : @ast.Expr,
) -> (String, ReceiverRecursionAccessKind, @token.Loc)? {
  match expr {
    @ast.Member(@ast.ThisExpr(_), name, member_loc) =>
      Some((name, ReceiverRecursionStatic, member_loc))
    @ast.ComputedMember(
      @ast.ThisExpr(_),
      @ast.StringLit(name, false, @token.LexForm::LexNormal, key_loc),
      member_loc
    ) =>
      Some((name, ReceiverRecursionComputedLiteral(key_loc, false), member_loc))
    @ast.ComputedMember(
      @ast.ThisExpr(_),
      @ast.StringLit(name, true, @token.LexForm::LexNormal, key_loc),
      member_loc
    ) =>
      Some((name, ReceiverRecursionComputedLiteral(key_loc, true), member_loc))
    _ => None
  }
}

///|
fn receiver_recursion_ident_member_access(
  expr : @ast.Expr,
  expected_base : String,
) -> (String, ReceiverRecursionAccessKind, @token.Loc)? {
  match expr {
    @ast.Member(@ast.Ident(base, _), name, member_loc) if base == expected_base =>
      Some((name, ReceiverRecursionStatic, member_loc))
    @ast.ComputedMember(
      @ast.Ident(base, _),
      @ast.StringLit(name, false, @token.LexForm::LexNormal, key_loc),
      member_loc
    ) if base == expected_base =>
      Some((name, ReceiverRecursionComputedLiteral(key_loc, false), member_loc))
    @ast.ComputedMember(
      @ast.Ident(base, _),
      @ast.StringLit(name, true, @token.LexForm::LexNormal, key_loc),
      member_loc
    ) if base == expected_base =>
      Some((name, ReceiverRecursionComputedLiteral(key_loc, true), member_loc))
    _ => None
  }
}

///|
fn receiver_recursion_recipe_access_modes_match(
  recipe : ReceiverRecursionExpressionRecipe,
) -> Bool {
  receiver_recursion_access_mode_matches(
    recipe.marker_property_access,
    recipe.property_access,
  ) &&
  receiver_recursion_access_mode_matches(
    recipe.marker_property_access,
    recipe.marker_access,
  ) &&
  receiver_recursion_access_mode_matches(
    recipe.marker_property_access,
    recipe.recursive_access,
  ) &&
  receiver_recursion_access_mode_matches(
    recipe.marker_property_access,
    recipe.root_access,
  )
}

///|
fn classify_receiver_recursion_function(
  stmt : @ast.Stmt,
  receiver_name : String,
  root_property_name : String,
  marker_property_access : ReceiverRecursionAccessKind,
  property_access : ReceiverRecursionAccessKind,
  root_access : ReceiverRecursionAccessKind,
  root_member_loc : @token.Loc,
) -> (String, String, String, ReceiverRecursionExpressionRecipe)? {
  match stmt {
    @ast.FuncDecl(function_name, params, body, _, _) => {
      guard numeric_recursion_identifier_is_safe(function_name) &&
        function_name == root_property_name &&
        params.length() == 1 &&
        body.length() == 2 else {
        return None
      }
      let parameter = params[0]
      guard numeric_recursion_identifier_is_safe(parameter) &&
        parameter != function_name &&
        parameter != receiver_name else {
        return None
      }
      let (marker_name, marker_access, base_condition_loc, marker_member_loc) = match
        body[0] {
        @ast.IfStmt(
          @ast.Binary(
            @ast.EqEqEq,
            @ast.Ident(condition_parameter, _),
            base_condition,
            base_condition_loc
          ),
          @ast.ReturnStmt(Some(marker_expr), _),
          None,
          _
        ) if condition_parameter == parameter &&
          exact_numeric_recursion_number(base_condition, NUMERIC_RECURSION_BASE) =>
          match receiver_recursion_this_member_access(marker_expr) {
            Some((marker_name, marker_access, marker_member_loc)) =>
              (
                marker_name, marker_access, base_condition_loc, marker_member_loc,
              )
            None => return None
          }
        _ => return None
      }
      let (
        recursive_add_loc,
        recursive_access,
        recursive_member_loc,
        recursive_call_loc,
        recursive_subtract_loc,
      ) = match body[1] {
        @ast.ReturnStmt(
          Some(
            @ast.Binary(
              @ast.Add,
              increment,
              @ast.Call(recursive_expr, args, recursive_call_loc),
              recursive_add_loc
            )
          ),
          _
        ) if exact_numeric_recursion_number(increment, NUMERIC_RECURSION_STEP) => {
          let (recursive_property, recursive_access, recursive_member_loc) = match
            receiver_recursion_this_member_access(recursive_expr) {
            Some(found) => found
            None => return None
          }
          guard recursive_property == root_property_name &&
            receiver_recursion_access_mode_matches(
              marker_property_access, recursive_access,
            ) &&
            receiver_recursion_access_mode_matches(
              marker_access, recursive_access,
            ) &&
            receiver_recursion_access_mode_matches(
              property_access, recursive_access,
            ) &&
            receiver_recursion_access_mode_matches(
              root_access, recursive_access,
            ) &&
            args.length() == 1 else {
            return None
          }
          match args[0] {
            @ast.Binary(
              @ast.Sub,
              @ast.Ident(step_parameter, _),
              decrement,
              recursive_subtract_loc
            ) if step_parameter == parameter &&
              exact_numeric_recursion_number(decrement, NUMERIC_RECURSION_STEP) =>
              (
                recursive_add_loc, recursive_access, recursive_member_loc, recursive_call_loc,
                recursive_subtract_loc,
              )
            _ => return None
          }
        }
        _ => return None
      }
      Some(
        (
          function_name,
          parameter,
          marker_name,
          ReceiverRecursionExpressionRecipe(
            base_condition_loc~,
            marker_property_access~,
            marker_member_loc~,
            marker_access~,
            recursive_add_loc~,
            property_access~,
            recursive_member_loc~,
            recursive_access~,
            recursive_call_loc~,
            recursive_subtract_loc~,
            root_access~,
            root_member_loc~,
          ),
        ),
      )
    }
    _ => None
  }
}

///|
#warnings("-unused_value")
fn classify_receiver_recursion_program(
  stmts : Array[@ast.Stmt],
) -> ReceiverRecursionPlan? {
  guard stmts.length() == 3 else { return None }
  let (
    receiver_name,
    marker_name,
    marker_property_access,
    function_name,
    root_property_name,
    property_access,
    root_access,
    root_member_loc,
  ) = match stmts[1] {
    @ast.VarDecl(
      @ast.LetKind,
      receiver_name,
      Some(@ast.ObjectLit(properties, _)),
      _
    ) => {
      guard numeric_recursion_identifier_is_safe(receiver_name) &&
        properties.length() == 2 else {
        return None
      }
      let (marker_name, marker_property_access) = match
        receiver_recursion_property_access(properties[0]) {
        Some(found) => found
        None => return None
      }
      guard properties[0].kind == @ast.Init &&
        exact_numeric_recursion_number(
          properties[0].value,
          NUMERIC_RECURSION_BASE,
        ) else {
        return None
      }
      let (root_property_name, property_access) = match
        receiver_recursion_property_access(properties[1]) {
        Some(found) => found
        None => return None
      }
      let function_name = match properties[1].value {
        @ast.Ident(value_name, _) => value_name
        _ => return None
      }
      guard properties[1].kind == @ast.Init &&
        function_name == root_property_name &&
        numeric_recursion_identifier_is_safe(function_name) else {
        return None
      }
      match stmts[2] {
        @ast.ExprStmt(@ast.Call(root_member_expr, args, _), _) if args.length() ==
          1 => {
          let (root_property_name_in_call, root_access, root_member_loc) = match
            receiver_recursion_ident_member_access(
              root_member_expr, receiver_name,
            ) {
            Some(found) => found
            None => return None
          }
          guard root_property_name_in_call == root_property_name else {
            return None
          }
          match args[0] {
            @ast.NumberLit(value, @token.LexForm::LexNormal, _) if value ==
              NUMERIC_RECURSION_INITIAL_ARGUMENT =>
              (
                receiver_name, marker_name, marker_property_access, function_name,
                root_property_name, property_access, root_access, root_member_loc,
              )
            _ => return None
          }
        }
        _ => return None
      }
    }
    _ => return None
  }
  guard receiver_name != function_name && receiver_name != marker_name else {
    return None
  }
  guard function_name != marker_name else { return None }
  guard stmts[0] is @ast.FuncDecl(_, _, _, _, _) else { return None }
  match
    classify_receiver_recursion_function(
      stmts[0],
      receiver_name,
      root_property_name,
      marker_property_access,
      property_access,
      root_access,
      root_member_loc,
    ) {
    Some((actual_function_name, parameter, marker_name_from_body, recipe)) => {
      guard actual_function_name == function_name &&
        marker_name_from_body == marker_name else {
        return None
      }
      Some(
        ReceiverRecursionPlan(
          function_name~,
          receiver_name~,
          parameter~,
          property_name=root_property_name,
          marker_name~,
          initial_argument=NUMERIC_RECURSION_INITIAL_ARGUMENT,
          recipe~,
        ),
      )
    }
    None => None
  }
}

///|
#warnings("-unused_value")
fn receiver_recursion_root_statement_matches(
  plan : ReceiverRecursionPlan,
  index : Int,
  stmt : @ast.Stmt,
) -> Bool {
  match index {
    0 =>
      match stmt {
        @ast.FuncDecl(name, params, body, _, _) =>
          name == plan.function_name &&
          params.length() == 1 &&
          params[0] == plan.parameter &&
          body.length() == 2
        _ => false
      }
    1 =>
      match stmt {
        @ast.VarDecl(
          @ast.LetKind,
          receiver_name,
          Some(@ast.ObjectLit(properties, _)),
          _
        ) => {
          guard receiver_name == plan.receiver_name && properties.length() == 2 else {
            return false
          }
          let (first_name, first_access) = match
            receiver_recursion_property_access(properties[0]) {
            Some(found) => found
            None => return false
          }
          let (second_name, second_access) = match
            receiver_recursion_property_access(properties[1]) {
            Some(found) => found
            None => return false
          }
          first_name == plan.marker_name &&
          second_name == plan.property_name &&
          receiver_recursion_access_matches(
            plan.recipe.marker_property_access,
            first_access,
          ) &&
          receiver_recursion_access_matches(
            plan.recipe.property_access,
            second_access,
          ) &&
          exact_numeric_recursion_number(
            properties[0].value,
            NUMERIC_RECURSION_BASE,
          ) &&
          (match properties[1].value {
            @ast.Ident(name, _) => name == plan.function_name
            _ => false
          })
        }
        _ => false
      }
    2 =>
      match stmt {
        @ast.ExprStmt(@ast.Call(root_member_expr, args, _), _) => {
          let (property_name, root_access, root_member_loc) = match
            receiver_recursion_ident_member_access(
              root_member_expr,
              plan.receiver_name,
            ) {
            Some(found) => found
            None => return false
          }
          property_name == plan.property_name &&
          receiver_recursion_access_matches(
            plan.recipe.root_access,
            root_access,
          ) &&
          root_member_loc == plan.recipe.root_member_loc &&
          args.length() == 1 &&
          exact_numeric_recursion_number(args[0], plan.initial_argument)
        }
        _ => false
      }
    _ => false
  }
}

///|
#warnings("-unused_value")
fn receiver_recursion_body_statement_matches(
  plan : ReceiverRecursionPlan,
  index : Int,
  stmt : @ast.Stmt,
) -> Bool {
  match index {
    0 =>
      match stmt {
        @ast.IfStmt(
          @ast.Binary(
            @ast.EqEqEq,
            @ast.Ident(parameter, _),
            base_condition,
            base_condition_loc
          ),
          @ast.ReturnStmt(Some(marker_expr), _),
          None,
          _
        ) => {
          let (marker_name, marker_access, marker_member_loc) = match
            receiver_recursion_this_member_access(marker_expr) {
            Some(found) => found
            None => return false
          }
          parameter == plan.parameter &&
          marker_name == plan.marker_name &&
          receiver_recursion_access_matches(
            plan.recipe.marker_access,
            marker_access,
          ) &&
          base_condition_loc == plan.recipe.base_condition_loc &&
          marker_member_loc == plan.recipe.marker_member_loc &&
          exact_numeric_recursion_number(base_condition, NUMERIC_RECURSION_BASE)
        }
        _ => false
      }
    1 =>
      match stmt {
        @ast.ReturnStmt(
          Some(
            @ast.Binary(
              @ast.Add,
              increment,
              @ast.Call(recursive_expr, args, recursive_call_loc),
              recursive_add_loc
            )
          ),
          _
        ) => {
          let (property_name, recursive_access, recursive_member_loc) = match
            receiver_recursion_this_member_access(recursive_expr) {
            Some(found) => found
            None => return false
          }
          guard property_name == plan.property_name &&
            receiver_recursion_access_matches(
              plan.recipe.recursive_access,
              recursive_access,
            ) &&
            recursive_add_loc == plan.recipe.recursive_add_loc &&
            recursive_member_loc == plan.recipe.recursive_member_loc &&
            recursive_call_loc == plan.recipe.recursive_call_loc &&
            exact_numeric_recursion_number(increment, NUMERIC_RECURSION_STEP) &&
            args.length() == 1 else {
            return false
          }
          match args[0] {
            @ast.Binary(
              @ast.Sub,
              @ast.Ident(parameter, _),
              decrement,
              recursive_subtract_loc
            ) =>
              parameter == plan.parameter &&
              recursive_subtract_loc == plan.recipe.recursive_subtract_loc &&
              exact_numeric_recursion_number(decrement, NUMERIC_RECURSION_STEP)
            _ => false
          }
        }
        _ => false
      }
    _ => false
  }
}

///|
#warnings("-unused_value")
fn receiver_recursion_plan_is_dispatchable(
  plan : ReceiverRecursionPlan,
) -> Bool {
  receiver_recursion_recipe_access_modes_match(plan.recipe)
}