unrot <- EFA(test_models$baseline$cormat, 3, N = 500)
vari <- .rotate_model(unrot, rotation = "varimax", type = "EFAtools")
vari_psych <- .rotate_model(unrot, rotation = "varimax", type = "psych")
vari_spss <- .rotate_model(unrot, rotation = "varimax", type = "SPSS")

unrot_1 <- EFA(test_models$baseline$cormat, 1, N = 500)
vari_1 <- suppressWarnings(.rotate_model(unrot_1, rotation = "varimax", type = "EFAtools"))

test_that("output class and dimensions are correct", {
  expect_type(vari, "list")
  expect_type(vari_1, "list")
  expect_named(vari, c("rot_loadings", "rotmat", "vars_accounted_rot", "settings"))
  expect_named(vari_1, c("rot_loadings", "rotmat", "vars_accounted_rot", "settings"))

  expect_s3_class(vari$rot_loadings, "LOADINGS")
  checkmate::expect_matrix(vari$rotmat)
  checkmate::expect_matrix(vari$vars_accounted_rot)
  expect_type(vari$settings, "list")

  expect_s3_class(vari_1$rot_loadings, "LOADINGS")
  expect_equal(vari_1$rotmat, NA)
  expect_null(vari_1$vars_accounted_rot)
  expect_type(vari_1$settings, "list")
})

test_that("settings are returned correctly", {
  expect_named(vari$settings, c("normalize", "precision", "order_type",
                                "varimax_type"))
  expect_named(vari_psych$settings, c("normalize", "precision", "order_type",
                                      "varimax_type"))
  expect_named(vari_spss$settings, c("normalize", "precision", "order_type",
                                     "varimax_type"))
  expect_named(vari_1$settings, c("normalize", "precision", "order_type",
                                  "varimax_type"))

  expect_equal(vari$settings$normalize, TRUE)
  expect_equal(vari_psych$settings$normalize, TRUE)
  expect_equal(vari_spss$settings$normalize, TRUE)
  expect_equal(vari_1$settings$normalize, TRUE)

  expect_equal(vari$settings$precision, 1e-05)
  expect_equal(vari_psych$settings$precision, 1e-05)
  expect_equal(vari_spss$settings$precision, 1e-5)
  expect_equal(vari_1$settings$precision, 1e-05)

  expect_equal(vari$settings$order_type, "eigen")
  expect_equal(vari_psych$settings$order_type, "eigen")
  expect_equal(vari_spss$settings$order_type, "ss_factors")
  expect_equal(vari_1$settings$order_type, "eigen")

  expect_equal(vari$settings$varimax_type, "kaiser")
  expect_equal(vari_psych$settings$varimax_type, "svd")
  expect_equal(vari_spss$settings$varimax_type, "kaiser")
  expect_equal(vari_1$settings$varimax_type, "kaiser")

})

test_that("errors etc. are thrown correctly", {

  expect_error(.rotate_model(unrot, rotation = "varimax", type = "none"), class = "efa_type_none")

  expect_warning(.rotate_model(unrot, rotation = "varimax", type = "EFAtools", normalize = FALSE), class = "efa_type_override")
  expect_warning(.rotate_model(unrot, rotation = "varimax", type = "EFAtools", order_type = "ss_factors"), class = "efa_type_override")

  expect_warning(.rotate_model(unrot, rotation = "varimax", type = "psych", normalize = FALSE), class = "efa_type_override")
  expect_warning(.rotate_model(unrot, rotation = "varimax", type = "psych", order_type = "ss_factors"), class = "efa_type_override")

  expect_warning(.rotate_model(unrot, rotation = "varimax", type = "SPSS", normalize = FALSE), class = "efa_type_override")
  expect_warning(.rotate_model(unrot, rotation = "varimax", type = "SPSS", order_type = "eigen"), class = "efa_type_override")
  expect_warning(.rotate_model(unrot, rotation = "varimax", type = "SPSS", varimax_type = "svd"), class = "efa_type_override")
  expect_warning(.rotate_model(unrot_1, rotation = "varimax", type = "EFAtools"), class = "efa_single_factor")
})

test_that(".VARIMAX_SPSS tolerates a zero-communality row (Kaiser normalisation)", {
  # A zero-communality row makes 1/sqrt(h2) = Inf; without floored weights Kaiser
  # normalisation injects NaN that corrupts the rotation or triggers an
  # uninformative if(NA) error. The floored row must stay zero throughout.
  L <- matrix(c(0.8, 0.1,
                0.7, 0.2,
                0.0, 0.0,
                0.1, 0.9,
                0.2, 0.8), ncol = 2, byrow = TRUE)
  res <- .VARIMAX_SPSS(L, normalize = TRUE)
  expect_false(anyNA(res$loadings))
  expect_equal(res$loadings[3, ], c(0, 0))
})

test_that("the svd varimax path tolerates a zero-communality row (Kaiser normalisation)", {
  # stats::varimax() Kaiser-normalises by dividing each row by sqrt(h2) without a floor,
  # so the same zero-communality row that .VARIMAX_SPSS() handles above makes the
  # rotation die inside La.svd with an uninformative, unclassed error. .varimax_svd()
  # normalises with the floored weights instead, so both varimax_type values accept the
  # same data rather than the choice of `type` deciding whether it errors or succeeds.
  L <- matrix(c(0.8, 0.1,
                0.7, 0.2,
                0.0, 0.0,
                0.1, 0.9,
                0.2, 0.8), ncol = 2, byrow = TRUE)

  res <- .varimax_svd(L, normalize = TRUE)
  expect_false(anyNA(res$loadings))
  expect_equal(unclass(res$loadings)[3, ], c(0, 0))
  # the reproduction identity loadings == L %*% rotmat survives the pre/post scaling
  expect_equal(unclass(res$loadings), L %*% res$rotmat, ignore_attr = TRUE)

  # the floor is arithmetically neutral: with no zero-communality row the floored path
  # reproduces stats::varimax() exactly
  L2 <- L
  L2[3, ] <- c(0.6, 0.3)
  expect_equal(unclass(.varimax_svd(L2, normalize = TRUE)$loadings),
               unclass(stats::varimax(L2, normalize = TRUE, eps = 1e-5)$loadings))

  # normalize = FALSE skips Kaiser normalisation entirely, so it must reach
  # stats::varimax() unnormalized rather than picking up the floored weights
  expect_equal(unclass(.varimax_svd(L2, normalize = FALSE)$loadings),
               unclass(stats::varimax(L2, normalize = FALSE, eps = 1e-5)$loadings))
  expect_false(isTRUE(all.equal(unclass(.varimax_svd(L2, normalize = FALSE)$loadings),
                                unclass(.varimax_svd(L2, normalize = TRUE)$loadings))))

  # the guard reaches both rotations that build on the svd varimax
  for (rn in c("varimax", "promax")) {
    out <- .rotate_model(list(unrot_loadings = L), rotation = rn, type = "psych")
    expect_false(anyNA(out$rot_loadings))
    expect_equal(unclass(out$rot_loadings)[3, ], c(0, 0), ignore_attr = TRUE)
  }
})

test_that("the SPSS varimax non-convergence warning names the tolerance alone", {
  # The Kaiser sweep runs a budget of 1000 iterations that is fixed in the algorithm, so
  # `maxit` is no remedy here and only `precision` is named. The branch is a backstop -- the
  # sweep drives the change in the simplicity criterion to exactly zero on every input tried,
  # so no correlation matrix reaches it -- and the warning is raised directly to make sure the
  # text it would show is the text recorded here.
  w <- tryCatch(.warn_rotation_no_convergence(FALSE, 1000L, tunable_maxit = FALSE),
                efa_rotation_no_convergence = function(w) w)
  expect_s3_class(w, "efa_rotation_no_convergence")
  expect_snapshot(cat(conditionMessage(w)))
})

rm(unrot, vari, unrot_1, vari_1, vari_psych, vari_spss)

