# approach C: stabilize residuals, compute local weighted variance, then take square root and shift to enforce nonnegative

# --- Weighted variance with a small-sample correction (Bessel-like) ---
wvar <- function(y, w) {
  w <- pmax(w, 0) # ensure weights nonnegative
  sw  <- sum(w)
  if (sw <= .Machine$double.eps) return(NA_real_)
  m   <- sum(w * y) / sw # weighted mean
  num <- sum(w * (y - m)^2) # weighted sum of squares around the mean
  # Unbiased-ish denom: sum(w) - sum(w^2)/sum(w)
  denom <- sw - sum(w^2) / sw
  if (denom > .Machine$double.eps) num / denom else num / sw
}

# --- kernels (univariate) ---
kern_fun <- function(u, kernel = c("gaussian","epanechnikov")) {
  kernel <- match.arg(kernel)
  if (kernel == "gaussian") {
    exp(-0.5*u^2) / sqrt(2*pi)
  } else {
    out <- 0.75 * (1 - u^2)
    out[abs(u) > 1] <- 0
    out
  }
}

# --- Moving-window variance with kernel weighting (fixed bandwidth h) ---
moving_var_kernel <- function(x, y, x0 = x, h, kernel = c("gaussian","epanechnikov"),
                              min_pts = 5) {
  kernel <- match.arg(kernel)
  x  <- as.numeric(x); y <- as.numeric(y); x0 <- as.numeric(x0)
  stopifnot(length(x) == length(y))
  if (length(h) != 1 || h <= 0) stop("Provide a positive scalar bandwidth h.")
  out <- numeric(length(x0))
  for (i in seq_along(x0)) {
    u  <- (x - x0[i]) / h # kernel scaling
    w  <- kern_fun(u, kernel = kernel)
    # ensure enough effective points
    if (sum(w > 0) < min_pts) {
      out[i] <- NA_real_
    } else {
      out[i] <- wvar(y, w)
    }
  }
  out
}

ARCHmXAdditive_backfitting_variance_smooth <- function(target_sum_nonpar, X, bandwidth = NULL,
                                        max_iter = 50, tol = 1e-4, floor_eps = 1e-8) {
  # target_sum_nonpar: vector of length n with the "total" nonparametric component
  #                    i.e., sigma2_resid = eps^2 - arch_component   (can be < 0)

  n <- nrow(X) # number of samples
  d <- ncol(X) # number of covariates
  
  # Initialize m values strictly positive (tiny), and storage for functions
  m_values <- matrix(floor_eps, nrow = n, ncol = d)
  m_funcs  <- vector("list", d)
  
  
  for (iter in 1:max_iter) {
    m_old <- m_values
    
    for (ell in 1:d) {
      # Partial residual for component ell
      partial_resid <- target_sum_nonpar - rowSums(m_values[, -ell, drop = FALSE])
      
      ########################################    
      # Variance Smoothing
      
      x = X[,ell]
      xx <- x #seq(min(x), max(x), length.out = 300)
      
      # Bandwidth via Silverman heuristic (works fine for illustration)
      h  <- 1.06 * sd(x) * n^(-1/5)
      
      # Kernel moving-variance (Gaussian kernel)
      v_hat_kernel <- moving_var_kernel(x, partial_resid, x0 = xx, h = h, kernel = "gaussian", min_pts = 10)
      #v_hat_kernel <- moving_var_kernel(x, sqrt(partial_resid - min(partial_resid)), x0 = xx, h = h, kernel = "gaussian", min_pts = 10)
      
      y_hat = sqrt(v_hat_kernel)-min(sqrt(v_hat_kernel))
      
      #cat("\n ####### backfitting var smooth ###### ell = ", ell)
      #cat("\n x = ",x[1:10])
      #cat("\n target = ", target_sum_nonpar[1:10])
      #cat("\n y_hat = ",y_hat[1:10])
      
      fit = list()
      fit$x = xx
      fit$y = y_hat
      ########################################        
      
      
      
      # Predict on observed X and exponentiate back
      ghat_at_x  <- approx(fit$x, fit$y, xout = X[, ell], rule = 2)$y
      m_values[, ell] <- ghat_at_x
      
      # Store the function on a grid
      m_funcs[[ell]] <- (function(xgrid, ygrid) {
        # returns m_ell(x) = exp(g_ell(x))
        f_lin <- approxfun(xgrid, ygrid, rule = 2)
        function(z) f_lin(z)
      })(fit$x, fit$y)
    }
    
    if (max(abs(m_values - m_old)) < tol) break
  }
  
  list(m_values = m_values, m_funcs = m_funcs, bandwidth = bandwidth)
}