#' Cross-validation for l0ara
#' @description Perform k-fold cross-validation over a supplied sequence of
#'   penalty values and return the value selected by the chosen measure.
#' @usage  cv.l0ara(x, y, family, lam, measure, nfolds, maxit, eps, seed)
#' @param x Input matrix as in \code{l0ara}.
#' @param y Response variable as in \code{l0ara}.
#' @param family Model family as in \code{l0ara}.
#' @param lam A user-supplied sequence of candidate penalty values. At least two
#'   values are required.
#' @param measure Criterion used to compare folds. Use \code{"mse"} or
#'   \code{"mae"} for any supported family. Use \code{"class"} or
#'   \code{"auc"} only with \code{family = "logit"}.
#' @param nfolds Number of folds. Default value is 10. Smallest value is 3.
#' @param maxit Maximum number of iterations passed to each call to
#'   \code{l0ara()}.
#' @param eps Convergence threshold. Default value is \code{1e-4}.
#' @param seed Optional random seed used to generate the fold assignments.
#' @details For each fold, the function fits one model per value in
#'   \code{lam}, evaluates the requested measure on the held-out data, and then
#'   averages the results across folds. For \code{measure = "auc"}, the selected
#'   value is the one with the largest score; for all other measures it is the
#'   one with the smallest score.
#' @return An object with S3 class "cv.l0ara" containing:
#'  \item{cv.error}{Mean cross-validation score for each value in
#'    \code{lambda}.}
#'  \item{cv.std}{Estimated standard error of \code{cv.error}.}
#'  \item{lam.min}{The selected penalty value.}
#'  \item{lambda}{The supplied sequence of penalty values.}
#'  \item{measure}{The measure used for model selection.}
#'  \item{family}{The fitted model family.}
#'  \item{x}{The original design matrix.}
#'  \item{y}{The original response vector.}
#'  \item{name}{A printable label for \code{measure}.}
#'  \item{fit.min}{A \code{"l0ara"} object refit on the full data using
#'    \code{lam.min}.}
#' @author
#' Wenchuan Guo <wguo1017@gmail.com>, Shujie Ma <shujie.ma@ucr.edu>, Zhenqiu Liu <Zhenqiu.Liu@cshs.org>
#' @seealso \code{\link{l0ara}}, \code{\link{coef.cv.l0ara}},
#'   \code{\link{plot.cv.l0ara}}.
#' @examples
#' # Linear regression
#' # Generate design matrix and response variable
#' n <- 100
#' p <- 40
#' x <- matrix(rnorm(n*p), n, p)
#' beta <- c(1,0,2,3,rep(0,p-4))
#' noise <- rnorm(n)
#' y <- x%*%beta+noise
#' lam <- c(0.1, 0.3, 0.5)
#' fit <- cv.l0ara(x, y, family="gaussian", lam, measure = "mse")
#' @export

cv.l0ara <- function(x, y, family = c("gaussian", "logit", "gamma", "poisson", "inv.gaussian"), lam, measure = c("mse", "mae","class", "auc"), nfolds = 10,  maxit = 10^3, eps = 1e-04, seed){
  family <- match.arg(family)
  measure <- match.arg(measure)

  x <- .l0ara_as_matrix(x)
  y <- .l0ara_as_numeric(y)

  if (!is.null(lam) && length(lam) < 2) {
    stop("Need a sequence values for lambda", call. = FALSE)
  }
  if (nfolds < 3) {
    stop("nfolds must be bigger than 3", call. = FALSE)
  }
  if (!is.null(dim(x)) && length(y) != nrow(x)) {
    stop("length of y not equal to the number of rows of x", call. = FALSE)
  }
  if (!missing(seed)) {
    set.seed(seed)
  }
  if ((measure == "class" || measure == "auc") && family != "logit") {
    stop(sprintf("type='%s' can only be used with family='logit'", measure), call. = FALSE)
  }

  n <- nrow(x)
  p <- ncol(x)
  nlam <- length(lam)
  error <- matrix(NA_real_, nrow = nlam, ncol = nfolds)
  fold_id <- sample(rep(seq_len(nfolds), length.out = n))

  for (i in seq_len(nfolds)) {
    test_idx <- fold_id == i
    train_x <- x[!test_idx, , drop = FALSE]
    train_y <- y[!test_idx]
    test_x <- x[test_idx, , drop = FALSE]
    test_y <- y[test_idx]

    beta_matrix <- matrix(0, nrow = p, ncol = nlam)
    for (j in seq_len(nlam)) {
      beta_matrix[, j] <- .l0ara_fit_beta(
        x = train_x,
        y = train_y,
        family = family,
        lam = lam[j],
        maxit = maxit,
        eps = eps
      )
    }
    eta <- test_x %*% beta_matrix

    if (measure == "mse") {
      pred <- .l0ara_family_response(eta, family)
      error[, i] <- colMeans((pred - test_y) ^ 2)
    } else if (measure == "mae") {
      pred <- .l0ara_family_response(eta, family)
      error[, i] <- colMeans(abs(pred - test_y))
    } else if (measure == "class") {
      pred <- eta > 0
      error[, i] <- colMeans(pred != test_y)
    } else if (measure == "auc") {
      pred <- eta > 0
      neg_idx <- test_y == 0
      pos_idx <- test_y == 1
      fp <- colMeans(pred[neg_idx, , drop = FALSE])
      tp <- colMeans(pred[pos_idx, , drop = FALSE])
      error[, i] <- (tp - fp + 1) / 2
    }
  }

  cv.error <- rowMeans(error)
  centered <- error - cv.error
  cv.std <- sqrt(rowSums(centered * centered) / (nfolds - 1)) / sqrt(n)
  lam.min <- if (measure == "auc") lam[which.max(cv.error)] else lam[which.min(cv.error)]
  out <- list(
    cv.error = cv.error,
    cv.std = cv.std,
    lam.min = lam.min,
    measure = measure,
    lambda = lam,
    family = family,
    x = x,
    y = y,
    name = .l0ara_measure_label(measure),
    fit.min = structure(
      .l0ara_fit_raw(
        x = x,
        y = y,
        family = family,
        lam = lam.min,
        maxit = maxit,
        eps = eps
      ),
      class = "l0ara"
    )
  )
  class(out) <- "cv.l0ara"
  out
}
