#' @title Cronbach's Alpha for Items or Scales
#' @name cronbachs_alpha
#'
#' @description Compute various measures of internal consistencies for tests or
#' item-scales of questionnaires. `cronbachs_alpha()` calculates the Cronbach's
#' Alpha value for all variables in `x`. `item_alpha()` is an alias for
#' `cronbachs_alpha()`.
#'
#' @param x A matrix or a data frame, or an object of class `parameters_pca`,
#' as returned by [`parameters::principal_components()`], or an object of class
#' `parameters_efa`, as returned by `parameters::factor_analysis()`.
#' @param verbose Toggle warnings and messages.
#' @param ... Currently not used.
#'
#' @return The Cronbach's Alpha value for `x`.
#'
#' @note `item_alpha()` is an alias for `cronbachs_alpha()`.
#'
#' @details The Cronbach's Alpha value for `x`. A value closer to 1
#' indicates greater internal consistency, where usually following
#' rule of thumb is applied to interpret the results:
#' - \ifelse{html}{\out{&alpha;}}{\eqn{\alpha}{alpha}} < 0.5 is unacceptable,
#' - 0.5 < \ifelse{html}{\out{&alpha;}}{\eqn{\alpha}{alpha}} < 0.6 is poor,
#' - 0.6 < \ifelse{html}{\out{&alpha;}}{\eqn{\alpha}{alpha}} < 0.7 is questionable,
#' - 0.7 < \ifelse{html}{\out{&alpha;}}{\eqn{\alpha}{alpha}} < 0.8 is acceptable,
#' - and everything > 0.8 is good or excellent.
#'
#' @references Bland, J. M., and Altman, D. G. Statistics notes: Cronbach's
#'   alpha. BMJ 1997;314:572. 10.1136/bmj.314.7080.572
#'
#' @examples
#' data(mtcars)
#' x <- mtcars[, c("cyl", "gear", "carb", "hp")]
#' cronbachs_alpha(x)
#' @export
cronbachs_alpha <- function(x, ...) {
  UseMethod("cronbachs_alpha")
}


#' @rdname cronbachs_alpha
#' @export
item_alpha <- cronbachs_alpha


#' @rdname cronbachs_alpha
#' @export
cronbachs_alpha.data.frame <- function(x, verbose = TRUE, ...) {
  # remove missings
  .data <- stats::na.omit(x)

  # we need at least two columns for Cronach's Alpha
  if (is.null(ncol(.data)) || ncol(.data) < 2) {
    if (verbose) {
      insight::format_alert("Too few columns in `x` to compute Cronbach's Alpha.")
    }
    return(NULL)
  }

  # Compute Cronbach's Alpha
  dim(.data)[2] /
    (dim(.data)[2] - 1) *
    (1 - sum(apply(.data, 2, stats::var)) / stats::var(rowSums(.data)))
}


#' @export
cronbachs_alpha.matrix <- function(x, verbose = TRUE, ...) {
  cronbachs_alpha(as.data.frame(x), verbose = verbose, ...)
}


#' @export
cronbachs_alpha.parameters_pca <- function(x, verbose = TRUE, ...) {
  # fetch data used for the PCA
  pca_data <- attributes(x)$dataset

  # if NULL, can we get from environment?
  if (is.null(pca_data)) {
    pca_data <- attr(x, "data")
    if (is.null(pca_data)) {
      if (verbose) {
        insight::format_alert("Could not find data frame that was used for the PCA.")
      }
      return(NULL)
    }
    pca_data <- get(pca_data, envir = parent.frame())
  }

  # get assignment of columns to extracted components, based on the max loading
  factor_assignment <- attributes(x)$closest_component

  # sort and get unique IDs so we only get data from relevant columns
  unique_factors <- sort(unique(factor_assignment))

  # apply cronbach's alpha for each component,
  # only for variables with max loading
  cronb <- sapply(unique_factors, function(i) {
    cronbachs_alpha(
      pca_data[, as.vector(x$Variable[factor_assignment == i]), drop = FALSE],
      verbose = verbose,
      ...
    )
  })

  names(cronb) <- paste0("PC", unique_factors)
  unlist(cronb)
}


#' @export
cronbachs_alpha.parameters_efa <- cronbachs_alpha.parameters_pca
