# helper to compute nonparametric additive term 
m_sum <- function(row_vector, m, d) {
  s <- 0
  for (ell in 1:d) {
    val <- tryCatch(m[[ell]](row_vector[ell]), error = function(e) NA)
    if (!is.numeric(val) || length(val) != 1 || is.na(val) || is.infinite(val)) {
      stop(paste0("m[[", ell, "]] must return a single finite numeric value"))
    }
    if (val < 0){
      stop(paste0("m[[", ell, "]] must be nonnegative.")) # assumption in paper
    } 
    s <- s + val
  }
  return(s)
}

sim.ARCHmXAdditive <- function(n, alpha, m, X,  delta=2, valinit = 200, shock.distr = "Normal") {
  # Validation
  
  # NOTE: It is assumed that each m produces positive output. If non-positive, the function exits.
  # This occurs in a later check. 
  
  if (!(shock.distr %in% c("Normal", "t5", "t7", "Laplace"))){
    stop("shock.distr must be Normal, t5, t7, or Laplace")
  } 
  if (length(alpha) == 0) {
    stop("alpha must contain at least a single parameter")
  }
  if (any(alpha < 0)) {
    stop("alpha must be nonnegative") # assumption in paper 
  } 
  if (delta <= 0) {
    stop("delta must be a positive number.")
  }  
  if (!(is.matrix(X)) || (length(X) == 0)) {
    stop("X is not a valid data matrix.")
  }
  if(!is.list(m) || length(m) != dim(X)[2]){
    stop("m must be a list with the same number of covariates as in X.")
  }
  if (any(!vapply(m, is.function, logical(1)))) {
    stop("all elements of m must be functions.")
  }
  if(dim(X)[1] != (n +  valinit)){
    stop("Number of rows of X should be n + valinit")
  }
  
  p <- length(alpha) # number of ARCH lags
  d <- dim(X)[2] # number of covariates
  epsilons <- rep(0, n + valinit)
  
  # Step 2: Burn-in period 
  # ========================
  # Here we first need to simulate the data matrix for 
  # our burn-in epsilons (we will actually create zero 
  # rows for the initial p-1 epsilons as well to keep  
  # the indices consistent later on)
  
  # covariate_means <- colMeans(X)
  # covariate_sds <- apply(X, 2, sd)
  # simulated_data <- matrix(0, nrow=p + valinit, ncol=d)
  # for (i in p:(p + valinit)) {
  #   for (j in 1:d) {
  #     simulated_data[i,j] <- rnorm(1, covariate_means[j], covariate_sds[j])
  #   }
  # }
  # 
  # # Then we will augment X with this simulated data
  # 
  # augmented_X <- rbind(simulated_data, X)
  
  # Next we perform the burn-in simulations: 
  for (t in (p + 1):(n + valinit)) {
    sigma_to_deltath <- sum(alpha[1:p] * (epsilons^delta)[(t-1):(t-p)]) + m_sum(X[t-1, ], m, d)
    if (!is.finite(sigma_to_deltath) || sigma_to_deltath <= 0) { 
      stop("non-positive or non-finite sigma^delta encountered during burn-in. check m and alpha.") 
    } # assumption in paper
    epsilons[t] <- ARCHmX_simulate_white_noise(shock.distr) * sigma_to_deltath^(1/delta)
  }
  
  # Step 3: Simulation
  # ========================  
  # Here we simulate the values to be returned
  
  # for (t in (p + valinit + 1):(p + valinit + n_samples)) {
  #   sigma_to_deltath <- sum(alpha[1:p] * (epsilons^delta)[(t-1):(t-p)]) + m_sum(augmented_X[t-1, ], m, d)
  #   if (!is.finite(sigma_to_deltath) || sigma_to_deltath <= 0) { 
  #     stop("non-positive or non-finite sigma^delta encountered during burn-in. check m and alpha.") 
  #   } # assumption in paper
  #   epsilons[t] <- ARCHmX_simulate_white_noise(shock.distr) * sigma_to_deltath^(1/delta)
  # }  
  
  # And return them as a list
  i <- valinit + 1
  j <- valinit + n
  
  eps <- epsilons[i:j]
  X <- X[i:j, ]
  
  return(list(eps = eps, X = X))
}

