#' Estimate observation weights for measurement error models
#'
#' Computes observation-specific weights using uniform, minimax, and
#' quasi-likelihood weighting schemes based on replicate measurements of
#' contaminated covariates.
#'
#' @param W Numeric array of dimension \code{n x p x J} containing replicate
#'   measurements of contaminated covariates. Missing replicates should be
#'   coded as \code{NA}.
#' @param method Character string specifying which weights to return.
#'   One of \code{"all"}, \code{"uniform"}, \code{"minimax"},
#'   or \code{"quasi-likelihood"}.
#' @param gamma Optional non-negative regularisation parameter used in the
#'   quasi-likelihood weights. Defaults to \code{log(n)}.
#'
#' @return A numeric matrix with \code{n} rows. Columns correspond to the
#'   requested weighting schemes.
#'
#'
#'@keywords internal
estimating_weight <- function(
    W,
    method = c("uniform", "minimax", "quasi-likelihood"),
    gamma = NULL
) {
  n <- dim(W)[1]
  p <- dim(W)[2]
  J <- dim(W)[3]

  method <- match.arg(method)

  stopifnot(length(dim(W)) == 3)

  ## Early exit if no replicates
  has_rep <- apply(W[, 1, , drop = FALSE], 1, function(x) sum(!is.na(x)) > 1)

  ## Uniform weights
  w_uniform <- rep(1 / n, n)

  if (!any(has_rep)) {
    warning("No replicate information detected. Returning uniform weights.")
    return(matrix(w_uniform, ncol = 1, dimnames = list(NULL, "uniform")))
  }

  n <- dim(W)[1]
  p <- dim(W)[2]
  J <- dim(W)[3]

  if (is.null(gamma)) gamma <- log(n)

  ## Estimate Sigma_X
  Wbar <- apply(W, c(1, 2), mean, na.rm = TRUE)

  SigmaU_bar = estimating_SigmaU(W)[[1]]
  mean_SigmaU_bar = estimating_SigmaU(W)[[2]]

  SigmaX_hat <- cov(Wbar) - mean_SigmaU_bar

  ## Replace NA SigmaU_bar with mean_SigmaU_bar
  SigmaU_use <- SigmaU_bar

  for (i in seq_len(n)) {
    if (any(is.na(SigmaU_use[i, , ]))) {
      SigmaU_use[i, , ] <- mean_SigmaU_bar
    }
  }


  ## Minimax weights (Equation 3.4)

  lambda_max <- numeric(n)
  for (i in 1:n) {

    SigmaW_i <- SigmaX_hat + SigmaU_use[i, , ]
    lambda_max[i] <- max(eigen(SigmaW_i, symmetric = TRUE)$values)
  }

  w_minimax <- (1 / lambda_max)
  w_minimax <- w_minimax / sum(w_minimax)

  ## Quasi-likelihood weights

  A1 <- rep(0, p)
  A2 <- matrix(0, p, p)

  SigmaW <- array(0, dim = c(n, p, p))
  for (i in 1:n) {
    SigmaW[i, , ] <- SigmaX_hat + SigmaU_use[i, , ]
    Si_inv <- solve(SigmaW[i, , ])
    A1 <- A1 + as.numeric(Wbar[i, ] %*% Si_inv)
    A2 <- A2 + Si_inv
  }

  M1 <- rbind(
    cbind(
      Wbar %*% A2 %*% t(Wbar) +
        gamma * n * diag(n) -
        gamma * matrix(1, n, n),
      rep(1, n)
    ),
    c(rep(1, n), 0)
  )

  M2 <- c(Wbar %*% A1, 1)
  parms <- solve(M1, M2)

  w_ql <- parms[1:n]
  w_ql <- w_ql / sum(w_ql)

  ## Return

  if (method == "uniform") {
    return(matrix(w_uniform, ncol = 1, dimnames = list(NULL, "uniform")))
  }

  if (method == "minimax") {
    return(matrix(w_minimax, ncol = 1, dimnames = list(NULL, "minimax")))
  }

  if (method == "quasi-likelihood") {
    return(matrix(w_ql, ncol = 1, dimnames = list(NULL, "quasi_likelihood")))
  }
}

