// Closed admission for the first tree-walker ordinary-constructor slice.
// Only the exact fixed-parameter, numeric self/mutual-recursive body below is
// admitted.  Everything else remains on the legacy construction path.

///|
#warnings("-unused_constructor")
priv enum ConstructorRecursionTerminal {
  ConstructorRecursionNormal
  ConstructorRecursionReturn
  ConstructorRecursionThrow
}

///|
#warnings("-unused_field")
priv struct ConstructorRecursionFunctionPlan {
  name : String
  parameter : String
  value_property : String
  child_property : String
  recursive_callee : String
  condition_loc : @token.Loc
  subtract_loc : @token.Loc
  construct_loc : @token.Loc
  terminal : ConstructorRecursionTerminal
}

///|
fn ConstructorRecursionFunctionPlan::ConstructorRecursionFunctionPlan(
  name~ : String,
  parameter~ : String,
  value_property~ : String,
  child_property~ : String,
  recursive_callee~ : String,
  condition_loc~ : @token.Loc,
  subtract_loc~ : @token.Loc,
  construct_loc~ : @token.Loc,
  terminal~ : ConstructorRecursionTerminal,
) -> ConstructorRecursionFunctionPlan {
  {
    name,
    parameter,
    value_property,
    child_property,
    recursive_callee,
    condition_loc,
    subtract_loc,
    construct_loc,
    terminal,
  }
}

///|
fn constructor_recursion_terminal_expr_is_admissible(expr : @ast.Expr) -> Bool {
  match expr {
    @ast.NumberLit(_, _, _)
    | @ast.StringLit(_, _, _, _)
    | @ast.BoolLit(_, _)
    | @ast.NullLit(_)
    | @ast.UndefinedLit(_)
    | @ast.Ident(_, _)
    | @ast.ThisExpr(_) => true
    @ast.ObjectLit(properties, _) => {
      for property in properties {
        guard property.kind == @ast.Init &&
          !property.computed &&
          !property.is_method &&
          (
            property.value is @ast.NumberLit(_, _, _) ||
            property.value is @ast.StringLit(_, _, _, _) ||
            property.value is @ast.BoolLit(_, _) ||
            property.value is @ast.NullLit(_) ||
            property.value is @ast.UndefinedLit(_) ||
            property.value is @ast.Ident(_, _) ||
            property.value is @ast.ThisExpr(_)
          ) else {
          return false
        }
      }
      true
    }
    _ => false
  }
}

///|
fn constructor_recursion_terminals_match(
  left : ConstructorRecursionTerminal,
  right : ConstructorRecursionTerminal,
) -> Bool {
  match (left, right) {
    (ConstructorRecursionNormal, ConstructorRecursionNormal)
    | (ConstructorRecursionReturn, ConstructorRecursionReturn)
    | (ConstructorRecursionThrow, ConstructorRecursionThrow) => true
    _ => false
  }
}

///|
#warnings("-unused_field")
priv struct ConstructorRecursionPlan {
  functions : Array[ConstructorRecursionFunctionPlan]
  root_name : String
  parameter : String
  value_property : String
  initial_argument : Double
  root_member_loc : @token.Loc
}

///|
fn ConstructorRecursionPlan::ConstructorRecursionPlan(
  functions~ : Array[ConstructorRecursionFunctionPlan],
  root_name~ : String,
  parameter~ : String,
  value_property~ : String,
  initial_argument~ : Double,
  root_member_loc~ : @token.Loc,
) -> ConstructorRecursionPlan {
  {
    functions: functions.copy(),
    root_name,
    parameter,
    value_property,
    initial_argument,
    root_member_loc,
  }
}

///|
fn constructor_recursion_function_plan(
  stmt : @ast.Stmt,
) -> ConstructorRecursionFunctionPlan? {
  match stmt {
    @ast.FuncDecl(name, params, body, _, _) => {
      guard numeric_recursion_identifier_is_safe(name) &&
        params.length() == 1 &&
        numeric_recursion_identifier_is_safe(params[0]) &&
        params[0] != name &&
        (body.length() == 2 || body.length() == 3) else {
        return None
      }
      let parameter = params[0]
      let (value_property, value_loc) = match body[0] {
        @ast.ExprStmt(
          @ast.MemberAssign(
            @ast.ThisExpr(_),
            property,
            @ast.Ident(value_parameter, value_loc),
            _
          ),
          _
        ) if value_parameter == parameter => (property, value_loc)
        _ => return None
      }
      ignore(value_loc)
      let (
        child_property,
        recursive_callee,
        condition_loc,
        subtract_loc,
        construct_loc,
      ) = match body[1] {
        @ast.IfStmt(
          @ast.Binary(
            @ast.Gt,
            @ast.Ident(condition_parameter, _),
            @ast.NumberLit(base, @token.LexForm::LexNormal, condition_loc),
            _
          ),
          @ast.Block(
            [
              @ast.ExprStmt(
                @ast.MemberAssign(
                  @ast.ThisExpr(_),
                  child_property,
                  @ast.NewExpr(
                    @ast.Ident(recursive_callee, _),
                    [
                      @ast.Binary(
                        @ast.Sub,
                        @ast.Ident(subtract_parameter, _),
                        @ast.NumberLit(
                          decrement,
                          @token.LexForm::LexNormal,
                          subtract_loc
                        ),
                        _
                      ),
                    ],
                    construct_loc
                  ),
                  _
                ),
                _
              ),
            ],
            _
          ),
          None,
          _
        ) if condition_parameter == parameter &&
          subtract_parameter == parameter &&
          base == 0.0 &&
          decrement == 1.0 &&
          numeric_recursion_identifier_is_safe(recursive_callee) =>
          (
            child_property, recursive_callee, condition_loc, subtract_loc, construct_loc,
          )
        _ => return None
      }
      guard child_property != value_property else { return None }
      let terminal = if body.length() == 2 {
        ConstructorRecursionNormal
      } else {
        match body[2] {
          @ast.ReturnStmt(Some(expr), _) if constructor_recursion_terminal_expr_is_admissible(
              expr,
            ) => ConstructorRecursionReturn
          @ast.ReturnStmt(None, _) => ConstructorRecursionReturn
          @ast.ThrowStmt(expr, _) if constructor_recursion_terminal_expr_is_admissible(
              expr,
            ) => ConstructorRecursionThrow
          _ => return None
        }
      }
      Some(
        ConstructorRecursionFunctionPlan(
          name~,
          parameter~,
          value_property~,
          child_property~,
          recursive_callee~,
          condition_loc~,
          subtract_loc~,
          construct_loc~,
          terminal~,
        ),
      )
    }
    _ => None
  }
}

///|
fn constructor_recursion_plan_is_closed(
  functions : Array[ConstructorRecursionFunctionPlan],
  root_name : String,
) -> Bool {
  guard functions.length() == 1 || functions.length() == 2 else { return false }
  guard functions[0].name == root_name else { return false }
  guard functions[0].recursive_callee == root_name || functions.length() == 2 else {
    return false
  }
  if functions.length() == 2 {
    guard functions[1].name != functions[0].name else { return false }
    guard functions[0].recursive_callee == functions[1].name &&
      functions[1].recursive_callee == functions[0].name else {
      return false
    }
    guard functions[0].parameter == functions[1].parameter &&
      functions[0].value_property == functions[1].value_property &&
      functions[0].child_property == functions[1].child_property &&
      constructor_recursion_terminals_match(
        functions[0].terminal,
        functions[1].terminal,
      ) else {
      return false
    }
  }
  true
}

///|
#warnings("-unused_value")
fn classify_constructor_recursion_program(
  stmts : Array[@ast.Stmt],
) -> ConstructorRecursionPlan? {
  guard stmts.length() == 2 || stmts.length() == 3 else { return None }
  let declaration_count = stmts.length() - 1
  let functions : Array[ConstructorRecursionFunctionPlan] = []
  for index in 0.. functions.push(plan)
      None => return None
    }
  }
  let (root_name, initial_argument, root_member_loc) = match
    stmts[declaration_count] {
    @ast.ExprStmt(
      @ast.Member(
        @ast.NewExpr(
          @ast.Ident(name, _),
          [@ast.NumberLit(value, @token.LexForm::LexNormal, _)],
          _
        ),
        property,
        root_member_loc
      ),
      _
    ) => (name, value, root_member_loc)
    _ => return None
  }
  guard constructor_recursion_plan_is_closed(functions, root_name) else {
    return None
  }
  let parameter = functions[0].parameter
  let value_property = functions[0].value_property
  let root_property = match stmts[declaration_count] {
    @ast.ExprStmt(@ast.Member(@ast.NewExpr(_, _, _), property, _), _) =>
      property
    _ => ""
  }
  guard value_property == root_property else { return None }
  Some(
    ConstructorRecursionPlan(
      functions~,
      root_name~,
      parameter~,
      value_property~,
      initial_argument~,
      root_member_loc~,
    ),
  )
}

///|
fn constructor_recursion_root_statement_matches(
  plan : ConstructorRecursionPlan,
  index : Int,
  stmt : @ast.Stmt,
) -> Bool {
  guard index >= 0 && index < plan.functions.length() else { return false }
  match stmt {
    @ast.FuncDecl(name, params, body, _, _) =>
      name == plan.functions[index].name &&
      params.length() == 1 &&
      params[0] == plan.functions[index].parameter &&
      body.length() ==
      (match plan.functions[index].terminal {
        ConstructorRecursionNormal => 2
        ConstructorRecursionReturn | ConstructorRecursionThrow => 3
      })
    _ => false
  }
}

///|
fn constructor_recursion_root_expression_matches(
  plan : ConstructorRecursionPlan,
  stmt : @ast.Stmt,
) -> Bool {
  let index = plan.functions.length()
  match stmt {
    @ast.ExprStmt(
      @ast.Member(
        @ast.NewExpr(
          @ast.Ident(name, _),
          [@ast.NumberLit(value, @token.LexForm::LexNormal, _)],
          _
        ),
        property,
        root_member_loc
      ),
      _
    ) =>
      name == plan.root_name &&
      property == plan.value_property &&
      value == plan.initial_argument &&
      root_member_loc == plan.root_member_loc &&
      index >= 0
    _ => false
  }
}

///|
fn constructor_recursion_body_statement_matches(
  plan : ConstructorRecursionPlan,
  function_index : Int,
  index : Int,
  stmt : @ast.Stmt,
) -> Bool {
  guard function_index >= 0 && function_index < plan.functions.length() else {
    return false
  }
  match index {
    0 =>
      match stmt {
        @ast.ExprStmt(
          @ast.MemberAssign(
            @ast.ThisExpr(_),
            property,
            @ast.Ident(parameter, _),
            _
          ),
          _
        ) =>
          property == plan.functions[function_index].value_property &&
          parameter == plan.functions[function_index].parameter
        _ => false
      }
    1 =>
      match stmt {
        @ast.IfStmt(
          @ast.Binary(
            @ast.Gt,
            @ast.Ident(condition_parameter, _),
            @ast.NumberLit(base, @token.LexForm::LexNormal, condition_loc),
            _
          ),
          @ast.Block(
            [
              @ast.ExprStmt(
                @ast.MemberAssign(
                  @ast.ThisExpr(_),
                  child_property,
                  @ast.NewExpr(
                    @ast.Ident(recursive_callee, _),
                    [
                      @ast.Binary(
                        @ast.Sub,
                        @ast.Ident(subtract_parameter, _),
                        @ast.NumberLit(
                          decrement,
                          @token.LexForm::LexNormal,
                          subtract_loc
                        ),
                        _
                      ),
                    ],
                    construct_loc
                  ),
                  _
                ),
                _
              ),
            ],
            _
          ),
          None,
          _
        ) =>
          condition_parameter == plan.functions[function_index].parameter &&
          base == 0.0 &&
          condition_loc == plan.functions[function_index].condition_loc &&
          child_property == plan.functions[function_index].child_property &&
          recursive_callee == plan.functions[function_index].recursive_callee &&
          subtract_parameter == plan.functions[function_index].parameter &&
          decrement == 1.0 &&
          subtract_loc == plan.functions[function_index].subtract_loc &&
          construct_loc == plan.functions[function_index].construct_loc
        _ => false
      }
    2 =>
      match (plan.functions[function_index].terminal, stmt) {
        (ConstructorRecursionReturn, @ast.ReturnStmt(Some(expr), _)) =>
          constructor_recursion_terminal_expr_is_admissible(expr)
        (ConstructorRecursionReturn, @ast.ReturnStmt(None, _)) => true
        (ConstructorRecursionThrow, @ast.ThrowStmt(expr, _)) =>
          constructor_recursion_terminal_expr_is_admissible(expr)
        _ => false
      }
    _ => false
  }
}