# S3 class functions for GPArotation objects:
#   .sortGPALoadings             --- internal helper, not exported
#   print.GPArotation
#   summary.GPArotation
#   print.summary.GPArotation

.sortGPALoadings <- function(x) {
  # Sort and sign-correct a GPArotation object.
  # Factors are reordered by descending variance explained and signs
  # are adjusted so that the sum of loadings per factor is positive.
  # Adapted from factanal function sortLoadings (R Core Team).
  # Called internally by print.GPArotation and summary.GPArotation.
  #
  # Args:
  #   x : a GPArotation object
  #
  # Returns:
  #   x with loadings, Th, and Phi updated consistently

  # Preserve original column names for restoration after reordering
  cln <- colnames(x$loadings)

  # Variance explained per factor:
  # orthogonal: sum of squared loadings per column
  # oblique: diagonal of Phi %*% t(L) %*% L (accounts for factor correlations)
  vx <- if (x$orthogonal)
    colSums(x$loadings^2)
  else
    diag(x$Phi %*% crossprod(x$loadings))

  # Reorder factors from largest to smallest variance explained
  io.ssq     <- order(vx, decreasing = TRUE)
  x$loadings <- x$loadings[, io.ssq, drop = FALSE]

  # Sign correction: flip any factor column whose loadings sum to negative,
  # so that the dominant loadings are positive (matches factanal convention)
  neg               <- colSums(x$loadings) < 0
  x$loadings[, neg] <- -x$loadings[, neg]

  # Restore column names after reordering
  colnames(x$loadings) <- cln

  # unit vector: 1 for columns kept as-is, -1 for sign-flipped columns
  # Used to apply the same transformation to Th and Phi
  unit <- c(1, -1)[neg + 1L]

  # Apply reordering and sign correction to rotation matrix Th
  # so that loadings = A %*% solve(t(Th)) remains consistent
  x$Th <- x$Th[, io.ssq] %*% diag(unit)

  # Apply reordering and sign correction to factor correlation matrix Phi
  # (oblique only) so that R = L %*% Phi %*% t(L) is preserved
  if (!x$orthogonal && "Phi" %in% names(x))
    x$Phi <- diag(unit) %*% x$Phi[io.ssq, io.ssq] %*% diag(unit)

  x
}


print.GPArotation <- function(x, digits = 3L, sortLoadings = TRUE,
                               rotateMat = FALSE, Table = FALSE, ...) {

  # --- Sort factors by descending variance explained ---
  # Uses .sortGPALoadings which aligns with factanal sorting convention
  if (sortLoadings) x <- .sortGPALoadings(x)

  # Column names and variance explained computed after sorting
  vx  <- if (x$orthogonal)
    colSums(x$loadings^2)
  else
    diag(x$Phi %*% t(x$loadings) %*% x$loadings)

  # --- Convergence header ---
  cat(if (x$orthogonal) "Orthogonal" else "Oblique")
  cat(" rotation method", x$method)
  cat(if (!x$convergence) " NOT")
  cat(" converged")
  cat(if ("randStartChar" %in% names(x)) " at lowest minimum.\n" else ".\n")

  # --- Random start diagnostics ---
  if ("randStartChar" %in% names(x)) {
    rs <- x$randStartChar
    cat("Of ", rs[1], " random starts ",
        round(100 * rs[2] / rs[1]), "% converged, ",
        round(100 * rs[3] / rs[1]), "% at the same lowest minimum.\n", sep = "")
    if (rs[4] > 1)
      cat("Random starts converged to ", rs[4], " different local minima.\n", sep = "")
  }

  # --- Loadings ---
  cat(if ("randStartChar" %in% names(x)) "Loadings at lowest minimum:\n" else "Loadings:\n")
  print(round(x$loadings, digits))

  # --- Variance explained ---
  varex <- rbind(`SS loadings` = vx)
  colnames(varex) <- colnames(x$loadings)  # no need for cln
  if (is.null(attr(x, "covariance"))) {
    varex <- rbind(varex, `Proportion Var` = vx / nrow(x$loadings))
    varex <- rbind(varex, `Cumulative Var`  = cumsum(vx / nrow(x$loadings)))
  }
  cat("\n")
  print(round(varex, digits))

  # --- Phi (oblique only) ---
  if (!x$orthogonal) {
    dimnames(x$Phi) <- list(colnames(x$loadings), colnames(x$loadings))
    cat("\nPhi:\n")
    print(round(x$Phi, digits))
  }

  # --- Optional rotation matrix ---
  if (rotateMat) {
    cat("\nRotating matrix:\n")
    print(t(solve(x$Th)), digits = digits)
  }

  # --- Optional iteration table ---
  if (Table) {
    cat("\nIteration table:\n")
    print(x$Table, digits = digits)
  }

  invisible(x)
}

summary.GPArotation <- function(object, digits = 3L, Structure = TRUE, ...) {
  # Returns a summary object with sign-corrected and sorted loadings,
  # Phi, method, convergence, iteration count, and display options.
  # Applies .sortGPALoadings for consistency with print.GPArotation ---
  # both print and summary show the same sorted, sign-corrected solution.

  object <- .sortGPALoadings(object)

  r <- list(
    loadings    = object$loadings,
    Phi         = object$Phi,
    method      = object$method,
    orthogonal  = object$orthogonal,
    convergence = object$convergence,
    iters       = rev(object$Table[, 1])[1],
    Structure   = Structure,
    digits      = digits
  )
  class(r) <- "summary.GPArotation"
  r
}

print.summary.GPArotation <- function(x, ...) {
  # --- Convergence header ---
  cat(if (x$orthogonal) "Orthogonal" else "Oblique")
  cat(" rotation method", x$method)
  if (!x$convergence) cat(" NOT")
  cat(" converged in ", x$iters, " iterations.\n", sep = "")

  # --- Pattern matrix (loadings) ---
  # For oblique rotation the pattern matrix contains regression coefficients:
  # the unique contribution of each factor to each item controlling for
  # factor intercorrelations. For orthogonal rotation pattern = structure.
  prstr <- !x$orthogonal && x$Structure
  cat(if (prstr) "Pattern (loadings):\n" else "Loadings:\n")
  print(round(x$loadings, x$digits))

  # --- Structure matrix (oblique only) ---
  # The structure matrix contains correlations between items and factors:
  # Structure = Pattern %*% Phi
  # Structure coefficients are inflated relative to pattern coefficients
  # when factor intercorrelations are high. Both are shown when
  # Structure = TRUE (the default) to support complete reporting.
  if (prstr) {
    cat("\nStructure:\n")
    S <- x$loadings %*% x$Phi
    colnames(S) <- colnames(x$loadings)
    print(round(S, x$digits))
  }
}