#' Inner ridge solver for binary logistic regression.
#'
#' Solves the t-parameterised inner problem via `glmnet` with `alpha = 0`.
#' Returns the coefficient vector (including intercept) at the optimum.
#'
#' @param t Numeric vector in (0, 1)^p_sel: relaxed inclusion vector for the
#'   selectable columns.
#' @param X Numeric matrix (no intercept column).
#' @param y Binary response in {0, 1}.
#' @param delta Current homotopy concentration parameter.
#' @param lambda Ridge penalty.
#' @param mand_idx Integer vector of mandatory column indices (1-based).
#' @param sel_idx Integer vector of selectable column indices (1-based).
#' @return Numeric vector: `(beta_0, beta_1, ..., beta_p)`.
#' @keywords internal
#' @noRd
solve_inner_binomial <- function(t, X, y, delta, lambda,
                                 mand_idx = integer(0),
                                 sel_idx = seq_len(ncol(X))) {
  p <- ncol(X)
  omega <- rep(0, p)
  if (length(mand_idx) > 0) {
    omega[mand_idx] <- lambda
  }
  omega[sel_idx] <- (lambda + delta) / (t^2) - delta
  omega <- pmax(omega, 1e-12)

  omega_sum <- sum(omega)
  if (omega_sum < 1e-12) omega_sum <- 1e-12
  penalty_factors <- p * omega / omega_sum
  lambda_glmnet <- 2 * omega_sum / p

  fit <- glmnet::glmnet(
    x = X, y = y, family = "binomial", alpha = 0,
    lambda = lambda_glmnet, penalty.factor = penalty_factors,
    intercept = TRUE, standardize = FALSE,
    thresh = 1e-12, maxit = 1e6
  )
  as.numeric(coef(fit))
}

#' Inner ridge solver for multinomial logistic regression.
#'
#' Uses `glmnet` with `family = "multinomial"` and grouped penalisation so
#' the same sparsity pattern is shared across classes.
#'
#' @inheritParams solve_inner_binomial
#' @param y Factor or integer-coded response.
#' @return Numeric matrix `(C, p + 1)` of class-by-coefficient values
#'   (column 1 is the intercept).
#' @keywords internal
#' @noRd
solve_inner_multinomial <- function(t, X, y, delta, lambda,
                                    mand_idx = integer(0),
                                    sel_idx = seq_len(ncol(X))) {
  p <- ncol(X)
  omega <- rep(0, p)
  if (length(mand_idx) > 0) {
    omega[mand_idx] <- lambda
  }
  omega[sel_idx] <- (lambda + delta) / (t^2) - delta
  omega <- pmax(omega, 1e-12)

  omega_sum <- sum(omega)
  if (omega_sum < 1e-12) omega_sum <- 1e-12
  penalty_factors <- p * omega / omega_sum
  lambda_glmnet <- 2 * omega_sum / p

  fit <- glmnet::glmnet(
    x = X, y = y, family = "multinomial",
    type.multinomial = "grouped", alpha = 0,
    lambda = lambda_glmnet, penalty.factor = penalty_factors,
    intercept = TRUE, standardize = FALSE,
    thresh = 1e-10, maxit = 1e5
  )
  cf <- coef(fit)
  do.call(rbind, lapply(cf, function(m) as.numeric(m)))
}

#' Inner ridge solver for linear (Gaussian) regression.
#'
#' Uses `glmnet` with `family = "gaussian"` and `alpha = 0`. Returns the
#' coefficient vector (including intercept) at the optimum.
#'
#' @inheritParams solve_inner_binomial
#' @param y Numeric response.
#' @return Numeric vector: `(beta_0, beta_1, ..., beta_p)`.
#' @keywords internal
#' @noRd
solve_inner_gaussian <- function(t, X, y, delta, lambda,
                                 mand_idx = integer(0),
                                 sel_idx = seq_len(ncol(X))) {
  p <- ncol(X)
  omega <- rep(0, p)
  if (length(mand_idx) > 0) {
    omega[mand_idx] <- lambda
  }
  omega[sel_idx] <- (lambda + delta) / (t^2) - delta
  omega <- pmax(omega, 1e-12)

  omega_sum <- sum(omega)
  if (omega_sum < 1e-12) omega_sum <- 1e-12
  penalty_factors <- p * omega / omega_sum
  lambda_glmnet <- 2 * omega_sum / p

  fit <- glmnet::glmnet(
    x = X, y = y, family = "gaussian", alpha = 0,
    lambda = lambda_glmnet, penalty.factor = penalty_factors,
    intercept = TRUE, standardize = FALSE,
    thresh = 1e-12, maxit = 1e6
  )
  as.numeric(coef(fit))
}

#' Danskin envelope gradient.
#'
#' Computes `df/dt_j = -2 (lambda + delta) * sum_c xi_{j,c}^2 / t_j^3`
#' where the sum collapses to a single term in the binomial / Gaussian case.
#'
#' @param xi Numeric vector (binomial / Gaussian) or matrix (multinomial)
#'   of inner coefficients including intercept(s).
#' @param t Numeric vector of relaxed inclusion values.
#' @param delta,lambda Current homotopy and ridge parameters.
#' @param sel_idx Selectable column indices in the original `X`.
#' @return Gradient vector of length `length(t)`.
#' @keywords internal
#' @noRd
grad_envelope <- function(xi, t, delta, lambda, sel_idx) {
  if (is.matrix(xi)) {
    # multinomial: column 1 is intercept; selectable columns are sel_idx + 1
    xi_sel <- xi[, sel_idx + 1, drop = FALSE]
    xi_sq_sum <- colSums(xi_sel^2)
  } else {
    xi_sel <- xi[sel_idx + 1]
    xi_sq_sum <- xi_sel^2
  }
  -2 * (lambda + delta) * xi_sq_sum / (t^3)
}
