#' Linear Regression with Errors-in-Variables Using Replicated Measurements
#'
#' Fits a linear regression model in the presence of measurement error in
#' covariates using replicated measurements and a combination of
#' phase-function estimation and generalized method of moments (GMM).
#'
#' The function provides an \code{lm}-like interface while internally handling
#' replicated error-prone covariates, measurement error correction, and
#' robust variance estimation.
#'
#'
#'
#' @param formula A symbolic description of the model to be fitted.
#'   Error-prone covariates must be wrapped in \code{W()} and error-free
#'   covariates must be wrapped in \code{Z()}.
#'
#'   The general form is:
#'   \preformatted{
#'     y ~ W(W1 + W2 + ...) + Z(Z1 + Z2 + ...)
#'   }
#'
#'   An intercept is included automatically unless removed explicitly.
#'
#' @param data A data frame containing the response variable and all covariates
#'   appearing in \code{formula}. Each row corresponds to one replicate
#'   measurement of a statistical unit.
#'
#'   The data frame must contain:
#'   \itemize{
#'     \item A column named \code{unit} identifying statistical units.
#'     \item One or more rows per \code{unit} if replicated measurements exist.
#'     \item One column for each error-prone covariate (appearing in \code{W()}).
#'     \item One column for each error-free covariate (appearing in \code{Z()}).
#'   }
#'
#'   Replicated measurements are represented by multiple rows sharing the
#'   same \code{unit} identifier. Error-free covariates and the response
#'   should be constant within each \code{unit}.
#'
#' @param weight_method Character string specifying the observation weighting
#'   method used in estimation. One of:
#'   \describe{
#'     \item{\code{"uniform"}}{Uniform weights across observations.}
#'     \item{\code{"minimax"}}{Minimax optimal weights.}
#'     \item{\code{"quasi-likelihood"}}{Quasi-likelihood-based weights (default recommended).}
#'   }
#'
#' @param B Integer specifying the number of bootstrap replications used
#'   to estimate the GMM weighting matrix. Defaults to \code{100}.
#'
#' @param t_grid_length Integer specifying the number of frequency grid points
#'   used in phase-function integration. Larger values improve numerical
#'   accuracy at the cost of computation time.
#'
#' @details
#' This function implements a measurement-error-corrected linear regression
#' estimator for models with replicated error-prone covariates.
#' When fewer than two units contain replicated measurements, the function
#' automatically falls back to a quadratic (identity-weight) estimator.
#' This ensures the model remains estimable even in the absence of
#' replication.
#'
#' The estimation procedure:
#' \enumerate{
#'   \item Aggregates replicated measurements into a structured array.
#'   \item Uses phase-function estimating equations to correct for unknown
#'         measurement error distributions.
#'   \item Combines moment conditions via GMM when sufficient replication
#'         information is available.
#'   \item Automatically switches to a quadratic (identity-weight) estimator
#'         when fewer than two statistical units contain replicated measurements.
#' }
#'
#' Variance estimation is performed using a sandwich estimator, with the
#' GMM weighting matrix estimated via a cluster bootstrap over statistical
#' units.
#'
#' @return An object of class \code{"eiv_mlr"} containing:
#' \describe{
#'   \item{coef}{Estimated regression coefficients.}
#'   \item{vcov}{Estimated variance-covariance matrix.}
#'   \item{se}{Standard errors of the estimates.}
#'   \item{zvalue}{Z-statistics for hypothesis testing.}
#'   \item{pvalue}{Two-sided p-values.}
#'   \item{fitted}{Fitted values at the unit level.}
#'   \item{method}{Estimation method used (GMM or quadratic fallback).}
#'   \item{n}{Number of statistical units.}
#' }
#'
#' Standard methods such as \code{summary()}, \code{coef()},
#' \code{vcov()}, \code{confint()}, \code{predict()}, and
#' \code{residuals()} are available for objects of this class.
#'
#' @examples
#' ## ------------------------------------------
#' ## Small reproducible example (for speed reasons, we chose a too small number of bootstrap samples)
#' ## ------------------------------------------
#'
#' set.seed(1)
#'
#' n  <- 30
#' J  <- 2
#'
#' unit <- rep(1:n, each = J)
#'
#' W_true <- rnorm(n)
#' W_obs  <- rep(W_true, each = J) + rnorm(n * J, sd = 0.5)
#'
#' Z1 <- rep(rnorm(n), each = J)
#' y  <- rep(1 + 2 * W_true - 0.5 * Z1[seq(1, n * J, by = J)], each = J) +
#'       rnorm(n * J)
#'
#' sim_data <- data.frame(
#'   unit = unit,
#'   y = y,
#'   W1 = W_obs,
#'   Z1 = Z1
#' )
#'
#' # For speed reasons, we use a very small number of bootstrap samples
#' fit <- eiv_mlr(
#'   y ~ W(W1) + Z(Z1),
#'   data = sim_data,
#'   B = 10,
#'   t_grid_length = 20
#' )
#'
#' coef(fit)
#' summary(fit)
#'
#'
#' ## ------------------------------------------
#' ## Additional examples (not run during checks)
#' ## ------------------------------------------
#'
#' \donttest{
#' ## ------------------------------------------
#' ## Example using included dataset
#' ## ------------------------------------------
#'
#' fit <- eiv_mlr(
#'   bmi ~ W(energy + protein + fat) + Z(age_in_month),
#'   data = dietary_white_women,
#'   weight_method = "minimax",
#'   B = 100,
#'   t_grid_length = 200
#' )
#'
#' summary(fit)
#' confint(fit)
#'
#'
#' ## ------------------------------------------
#' ## Simulated example with replication
#' ## ------------------------------------------
#'
#' set.seed(1)
#'
#' n  <- 200
#' J  <- 2
#'
#' unit <- rep(1:n, each = J)
#'
#' W_true <- rnorm(n)
#' W_obs  <- rep(W_true, each = J) + rnorm(n * J, sd = 0.5)
#'
#' Z1 <- rep(rnorm(n), each = J)
#' y  <- rep(1 + 2 * W_true - 0.5 * Z1[seq(1, n * J, by = J)], each = J) +
#'       rnorm(n * J)
#'
#' sim_data <- data.frame(
#'   unit = unit,
#'   y = y,
#'   W1 = W_obs,
#'   Z1 = Z1
#' )
#'
#' fit_rep <- eiv_mlr(
#'   y ~ W(W1) + Z(Z1),
#'   data = sim_data,
#'   B = 20
#' )
#'
#' summary(fit_rep)
#'
#'
#' ## ------------------------------------------
#' ## Simulated example without replication
#' ## ------------------------------------------
#'
#' sim_norep <- sim_data[!duplicated(sim_data$unit), ]
#'
#' fit_norep <- eiv_mlr(
#'   y ~ W(W1) + Z(Z1),
#'   data = sim_norep,
#'   B = 20
#' )
#'
#' summary(fit_norep)
#' }
#'
#' @export



eiv_mlr <- function(
    formula,
    data,
    weight_method = c("uniform", "minimax", "quasi-likelihood"),
    B = 100,
    t_grid_length = 1000
) {

  weight_method <- match.arg(weight_method)

  ## Parse formula
  f <- parse_eiv_formula(formula)

  ## Prepare data
  prep <- prepare_eiv_data(
    data   = data,
    y_name = f$y,
    W_names = f$W,
    Z_names = f$Z
  )

  ## Core estimator
  fit <- computing_GMM_estimator(
    W = prep$W,
    y = prep$y,
    Zmat = prep$Zmat,
    weight_method = weight_method,
    B = B,
    t_grid_length = t_grid_length
  )

  est <- fit$est
  vc  <- fit$var_est

  ## Name coefficients
  coef_names <- c(colnames(prep$Wbar), colnames(prep$Zmat))
  names(est) <- coef_names
  dimnames(vc) <- list(coef_names, coef_names)

  ## Put intercept first
  if ("(Intercept)" %in% names(est)) {
    new_order <- c("(Intercept)", setdiff(names(est), "(Intercept)"))
    est <- est[new_order]
    vc  <- vc[new_order, new_order, drop = FALSE]
  }

  se  <- sqrt(diag(vc))
  zval <- est / se
  pval <- 2 * pnorm(-abs(zval))

  ## Fitted values
  beta_W <- est[colnames(prep$Wbar)]
  beta_Z <- est[colnames(prep$Zmat)]

  fitted <- as.numeric(
    prep$Wbar %*% beta_W +
      prep$Zmat %*% beta_Z
  )

  ## Store Z design info for prediction
  Z_formula <- reformulate(f$Z, intercept = TRUE)

  terms_Z  <- terms(Z_formula, data = data)
  xlevels  <- .getXlevels(terms_Z, data)

  contrasts_Z <- attr(prep$Zmat, "contrasts")

  out <- list(
    call    = match.call(),
    formula = formula,
    coef    = est,
    vcov    = vc,
    se      = se,
    zvalue  = zval,
    pvalue  = pval,
    method  = fit$method,
    n       = length(prep$y),
    y       = prep$y,
    fitted  = fitted,
    W_names = colnames(prep$Wbar),
    Z_names = colnames(prep$Zmat),

    terms_Z = terms_Z,
    contrasts_Z = contrasts_Z,
    xlevels = xlevels
  )

  class(out) <- "eiv_mlr"
  out
}
