#' Column-wise L2 normalisation of a design matrix.
#'
#' Scales each column of `X` to unit L2 norm. Columns of zero norm are left
#' unchanged. Returned alongside the scaling vector so coefficients can be
#' rescaled if needed.
#'
#' @param X Numeric matrix.
#' @return List with `X_scaled` (the scaled matrix) and `v` (the original
#'   per-column L2 norms).
#' @keywords internal
#' @noRd
scale_design_matrix <- function(X) {
  v <- sqrt(colSums(X^2))
  v[v == 0] <- 1
  X_scaled <- sweep(X, 2, v, "/")
  list(X_scaled = X_scaled, v = v)
}

#' Power iteration for the largest eigenvalue of `t(X) %*% X`.
#'
#' Used to calibrate the Frank-Wolfe penalty schedule.
#'
#' @param X Numeric matrix.
#' @param n_iter Maximum number of power iterations.
#' @param tol Relative tolerance.
#' @return Estimate of `nu_max`.
#' @keywords internal
#' @noRd
compute_nu_max <- function(X, n_iter = 200, tol = 1e-8,seed = NULL) {
  if(!is.null(seed)){
    set.seed(seed)
  }
  p <- ncol(X)
  v <- rnorm(p)
  v <- v / sqrt(sum(v^2))
  nu_old <- 0
  for (i in seq_len(n_iter)) {
    Xv   <- X %*% v
    XtXv <- crossprod(X, Xv)
    nu   <- sum(Xv^2)
    v    <- as.numeric(XtXv)
    v    <- v / sqrt(sum(v^2))
    if (abs(nu - nu_old) < tol * max(nu, 1)) break
    nu_old <- nu
  }
  nu
}

#' Calibrate the Frank-Wolfe homotopy delta schedule.
#'
#' Computes `delta_min` and `delta_max` from the spectral norm of `X` and
#' the family-specific concentration constant.
#'
#' @param X_u Normalised design matrix (only the relaxed columns).
#' @param n Number of training rows.
#' @param family One of `"gaussian"`, `"binomial"`, `"multinomial"`.
#' @return List with `nu_max`, `delta_conc`, `delta_max`, `delta_min`.
#' @keywords internal
#' @noRd
calibrate_penalty_schedule <- function(X_u, n, family) {
  nu_max     <- compute_nu_max(X_u)
  delta_conc <- switch(family,
                       gaussian    = 3 * nu_max / (8 * n),
                       binomial    = nu_max / (8 * n),
                       multinomial = nu_max / (4 * n),
                       stop("Unknown family: ", family))
  delta_max  <- delta_conc
  delta_min  <- 1e-3 * delta_conc
  list(nu_max = nu_max, delta_conc = delta_conc,
       delta_max = delta_max, delta_min = delta_min)
}

#' Resolve the family argument, accepting `"linear"` as an alias for `"gaussian"`.
#'
#' @param family User-supplied family string.
#' @return One of `"gaussian"`, `"binomial"`, `"multinomial"`.
#' @keywords internal
#' @noRd
resolve_family <- function(family) {
  family <- tolower(family[1])
  if (family == "linear") family <- "gaussian"
  if (!family %in% c("gaussian", "binomial", "multinomial")) {
    stop("`family` must be one of \"gaussian\" (or \"linear\"), \"binomial\", \"multinomial\".",
         call. = FALSE)
  }
  family
}
