#' Advanced Principal Component Analysis (APCA)
#'
#' Performs PCA using one of nine different computational algorithms.
#' This is the main wrapper function for the apca package.
#'
#' @param data A numeric matrix or data frame.
#' @param n_components Integer. Number of principal components to extract.
#' @param method Character. The algorithm to use: "svd", "power", "qr", "nipals",
#'        "jacobi", "arnoldi", "als", "ppca", or "gha".
#' @param center Logical. Whether to mean-center the data. Default is TRUE.
#' @param scale Logical. Whether to scale variables to unit variance. Default is FALSE.
#' @return An object of class "apca" containing scores, loadings, and variance metrics.
#' @importFrom stats cov var rnorm
#' @importFrom graphics abline
#' @examples
#' # Run PCA using the classical SVD algorithm
#' data(mtcars)
#' res_svd <- apca(mtcars, n_components = 2, method = "svd", scale = TRUE)
#'
#' # Print basic information
#' print(res_svd)
#'
#' # ---------------------------------------------------------
#' # Extracting specific mathematical components
#' # ---------------------------------------------------------
#' # 1. Extract the PCA scores (useful for clustering/regression)
#' my_scores <- res_svd$scores
#' head(my_scores)
#'
#' # 2. Extract the loadings matrix (variable weights)
#' my_loadings <- res_svd$loadings
#' print(my_loadings)
#'
#' # 3. Extract the eigenvalues
#' my_eigen <- res_svd$eigenvalues
#' print(my_eigen)
#'
#' # Run PCA using the NIPALS algorithm
#' res_nipals <- apca(mtcars, n_components = 2, method = "nipals", scale = TRUE)
#' @export
apca <- function(data, n_components = 2, method = "svd", center = TRUE, scale = FALSE) {

  # 1. Basic Input Validation
  if (!is.matrix(data) && !is.data.frame(data)) {
    stop("Input 'data' must be a matrix or a data frame.")
  }

  X <- as.matrix(data)
  n_obs <- nrow(X)
  n_vars <- ncol(X)

  if (n_components > min(n_obs, n_vars)) {
    stop("n_components cannot exceed the number of variables or observations.")
  }

  # 2. Pre-processing
  X_prep <- scale(X, center = center, scale = scale)
  center_vals <- attr(X_prep, "scaled:center")
  scale_vals <- attr(X_prep, "scaled:scale")

  start_time <- Sys.time()

  # 3. Algorithm Routing
  result <- switch(method,
                   "svd"     = .apca_svd(X_prep, n_components),
                   "power"   = .apca_power(X_prep, n_components),
                   "qr"      = .apca_qr(X_prep, n_components),
                   "nipals"  = .apca_nipals(X_prep, n_components),
                   "jacobi"  = .apca_jacobi(X_prep, n_components),
                   "arnoldi" = .apca_arnoldi(X_prep, n_components),
                   "als"     = .apca_als(X_prep, n_components),
                   "ppca"    = .apca_ppca(X_prep, n_components),
                   "gha"     = .apca_gha(X_prep, n_components),
                   stop(paste("Method", method, "is not supported."))
  )

  end_time <- Sys.time()

  # 4. Finalizing Object
  result$computation_time <- as.numeric(difftime(end_time, start_time, units = "secs"))
  result$method <- method
  result$call <- match.call()
  result$dimension <- c(n_obs, n_vars)

  class(result) <- "apca"
  return(result)
}

#' @export
print.apca <- function(x, ...) {
  cat("\n-- Advanced PCA (APCA) Object --\n")
  cat("Method           :", toupper(x$method), "\n")
  cat("Components       :", ncol(x$scores), "\n")
  cat("Execution Time   :", round(x$computation_time, 6), "seconds\n")
  cat("----------------------------------\n")
  cat("Use summary() for detailed variance analysis.\n\n")
}

#' Summary Method for APCA Objects
#'
#' Extracts and calculates cumulative variance metrics from an APCA object.
#'
#' @param object An object of class "apca".
#' @param ... Additional arguments affecting the summary produced.
#' @return A list of class "summary.apca" containing variance explained,
#'         cumulative variance, and other specific algorithm metrics (like noise variance).
#' @examples
#' data(mtcars)
#' res <- apca(mtcars, n_components = 3, method = "svd", scale = TRUE)
#'
#' # Display cumulative variance summary
#' summary(res)
#' @export
summary.apca <- function(object, ...) {
  cum_var <- cumsum(object$variance_explained)

  res <- list(
    method = object$method,
    n_obs = object$dimension[1],
    n_vars = object$dimension[2],
    n_components = ncol(object$scores),
    var_explained = object$variance_explained,
    cum_var = cum_var,
    computation_time = object$computation_time,
    sigma_squared = object$sigma_squared
  )

  class(res) <- "summary.apca"
  return(res)
}

#' @export
print.summary.apca <- function(x, ...) {
  cat("\n================================================\n")
  cat("   Summary of APCA Results\n")
  cat("================================================\n")
  cat("Computational Method :", toupper(x$method), "\n")
  cat("Data Dimensions      :", x$n_obs, "obs x", x$n_vars, "vars\n")
  cat("Time Taken           :", round(x$computation_time, 6), "sec\n")

  if (!is.null(x$sigma_squared)) {
    cat("Noise Variance (s2)  :", round(x$sigma_squared, 6), "\n")
  }

  cat("------------------------------------------------\n")
  cat("Importance of Components:\n")

  importance <- rbind(
    "Proportion of Var" = round(x$var_explained, 5),
    "Cumulative Var"   = round(x$cum_var, 5)
  )
  colnames(importance) <- paste0("PC", 1:x$n_components)
  print(importance)
  cat("================================================\n\n")
}

#' Plot Method for APCA Objects
#'
#' Generates a scatter plot of the principal component scores.
#'
#' @param x An object of class "apca".
#' @param pc_x Integer. The principal component to plot on the X-axis (default: 1).
#' @param pc_y Integer. The principal component to plot on the Y-axis (default: 2).
#' @param col Color of the points (default: "steelblue").
#' @param pch Point character (default: 16).
#' @param cex Point size (default: 1.2).
#' @param las Axis label orientation (default: 1).
#' @param ... Additional graphical parameters passed to the base plot function.
#' @return No return value, called for side effects (plotting).
#' @examples
#' data(mtcars)
#' res <- apca(mtcars, n_components = 3, method = "svd", scale = TRUE)
#'
#' # Plot PC1 vs PC2 (Default)
#' plot(res)
#'
#' # Plot PC1 vs PC3 with custom base R graphical parameters
#' plot(res, pc_x = 1, pc_y = 3, col = "red", pch = 19)
#' @export
plot.apca <- function(x, pc_x = 1, pc_y = 2, col = "steelblue", pch = 16, cex = 1.2, las = 1, ...) {
  scores <- x$scores
  n_comp <- ncol(scores)

  # Validation: Ensure requested components are available in the object
  if (pc_x > n_comp || pc_y > n_comp) {
    stop(sprintf("Error: Object only contains %d components. Cannot plot PC%d.",
                 n_comp, max(pc_x, pc_y)))
  }

  # Extract data for X and Y axes
  x_data <- scores[, pc_x]
  y_data <- scores[, pc_y]

  # Get variance percentages for axis labels
  var_x <- round(x$variance_explained[pc_x] * 100, 2)
  var_y <- round(x$variance_explained[pc_y] * 100, 2)

  xlab <- sprintf("PC%d (%.2f%% variance)", pc_x, var_x)
  ylab <- sprintf("PC%d (%.2f%% variance)", pc_y, var_y)
  main_title <- sprintf("PCA Score Plot (Method: %s)", toupper(x$method))

  # Execute Plot with dynamic parameters
  plot(x_data, y_data,
       xlab = xlab,
       ylab = ylab,
       main = main_title,
       col = col,
       pch = pch,
       cex = cex,
       las = las,
       ...)

  # Add quadrant lines (zero origin)
  abline(h = 0, v = 0, lty = 2, col = "gray50")
}
