# ==============================================================================
# File: algo_classic.R
# Description: Internal functions for classical matrix decomposition methods
# (SVD, QR Algorithm, and Jacobi Eigenvalue Algorithm).
# ==============================================================================

# ------------------------------------------------------------------------------
# 1. Singular Value Decomposition (SVD) - Gold Standard
# ------------------------------------------------------------------------------
.apca_svd <- function(X, n_components) {
  n_obs <- nrow(X)

  # Perform SVD using base R
  svd_res <- svd(X)

  # Calculate eigenvalues from singular values
  eigenvalues <- (svd_res$d^2) / (n_obs - 1)

  # Calculate variance explained
  total_var <- sum(eigenvalues)
  var_explained <- eigenvalues[1:n_components] / total_var

  # Extract loadings and scores
  loadings <- svd_res$v[, 1:n_components, drop = FALSE]
  scores <- X %*% loadings

  # Formatting
  colnames(loadings) <- paste0("PC", 1:n_components)
  colnames(scores) <- paste0("PC", 1:n_components)

  list(
    scores = scores,
    loadings = loadings,
    eigenvalues = eigenvalues[1:n_components],
    variance_explained = var_explained
  )
}

# ------------------------------------------------------------------------------
# 2. QR Algorithm
# ------------------------------------------------------------------------------
.apca_qr <- function(X, n_components, max_iter = 1000, tol = 1e-9) {
  cov_matrix <- cov(X)
  p <- ncol(X)

  A <- cov_matrix
  V <- diag(p)

  for (iter in 1:max_iter) {
    qr_decomp <- qr(A)
    Q <- qr.Q(qr_decomp)
    R <- qr.R(qr_decomp)

    A_new <- R %*% Q
    V_new <- V %*% Q

    if (max(abs(diag(A_new) - diag(A))) < tol) {
      A <- A_new
      V <- V_new
      break
    }

    A <- A_new
    V <- V_new
  }

  eigenvalues <- diag(A)

  # Sort eigenvalues and eigenvectors in descending order
  idx <- order(eigenvalues, decreasing = TRUE)
  eigenvalues <- eigenvalues[idx]
  V <- V[, idx]

  # Filter components
  eigenvalues_final <- eigenvalues[1:n_components]
  loadings <- V[, 1:n_components, drop = FALSE]
  scores <- X %*% loadings

  total_var <- sum(diag(cov_matrix))
  var_explained <- eigenvalues_final / total_var

  colnames(loadings) <- paste0("PC", 1:n_components)
  colnames(scores) <- paste0("PC", 1:n_components)

  list(
    scores = scores,
    loadings = loadings,
    eigenvalues = eigenvalues_final,
    variance_explained = var_explained
  )
}

# ------------------------------------------------------------------------------
# 3. Jacobi Eigenvalue Algorithm
# ------------------------------------------------------------------------------
.apca_jacobi <- function(X, n_components, max_iter = 1000, tol = 1e-9) {
  cov_matrix <- cov(X)
  p <- ncol(X)

  A <- cov_matrix
  V <- diag(p)

  for (iter in 1:max_iter) {
    A_off <- A
    diag(A_off) <- 0
    max_idx <- which(abs(A_off) == max(abs(A_off)), arr.ind = TRUE)[1, ]
    i <- max_idx[1]
    j <- max_idx[2]

    # Convergence check
    if (abs(A[i, j]) < tol) break

    # Calculate Givens rotation angle
    if (A[i, i] == A[j, j]) {
      theta <- pi / 4
    } else {
      theta <- 0.5 * atan2(2 * A[i, j], A[i, i] - A[j, j])
    }

    c <- cos(theta)
    s <- sin(theta)

    R_mat <- diag(p)
    R_mat[i, i] <- c; R_mat[j, j] <- c
    R_mat[i, j] <- -s; R_mat[j, i] <- s

    # Update A and V
    A <- t(R_mat) %*% A %*% R_mat
    V <- V %*% R_mat
  }

  eigenvalues <- diag(A)
  idx <- order(eigenvalues, decreasing = TRUE)
  eigenvalues <- eigenvalues[idx]
  V <- V[, idx]

  eigenvalues_final <- eigenvalues[1:n_components]
  loadings <- V[, 1:n_components, drop = FALSE]
  scores <- X %*% loadings

  total_var <- sum(diag(cov_matrix))
  var_explained <- eigenvalues_final / total_var

  colnames(loadings) <- paste0("PC", 1:n_components)
  colnames(scores) <- paste0("PC", 1:n_components)

  list(
    scores = scores,
    loadings = loadings,
    eigenvalues = eigenvalues_final,
    variance_explained = var_explained
  )
}
