#' Estimate Marginal Contrasts
#'
#' Run a contrast analysis by estimating the differences between each level of a
#' factor. See also other related functions such as [estimate_means()]
#' and [estimate_slopes()].
#'
#' @param contrast A character vector indicating the name of the variable(s) for
#' which to compute the contrasts, optionally including representative values or
#' levels at which contrasts are evaluated (e.g., `contrast="x=c('a','b')"`).
#' **Note:** It is also possible to contrast average slopes, i.e. `contrast` can
#' be the name of two numeric predictors. However, while it is possible to
#' filter data for one numeric contrast (e.g., `contrast = c("num_pred=c(0, 1, 3)")`),
#' it is not possible to "filter" at certain values for two numeric predictors.
#' For contrasting slopes, the `comparison` will always be `"pairwise"`. It is
#' possible to compute pairwise comparisons of two average slopes at the levels
#' of a third variable, by also adding that variable to the `contrast` argument,
#' e.g. `contrast = c("num1", "num2", "factor")`. See 'Examples'.
#' @param p_adjust The p-values adjustment method for frequentist multiple
#' comparisons. Can be one of `"none"` (default), `"hochberg"`, `"hommel"`,
#' `"bonferroni"`, `"BH"`, `"BY"`, `"fdr"`, `"tukey"`, `"sidak"`, `"sup-t"`,
#' `"esarey"` or `"holm"`. The `"esarey"` option is specifically for the case of
#' Johnson-Neyman intervals, i.e. when calling `estimate_slopes()` with two
#' numeric predictors in an interaction term. `"sup-t"` computes simultaneous
#' confidence bands, also called sup-t confidence band (Montiel Olea &
#' Plagborg-Møller, 2019). Details for the other options can be found in the
#' p-value adjustment section of the `emmeans::test` documentation or
#' `?stats::p.adjust`. Note that certain options provided by the **emmeans**
#' package are only available if you set `backend = "emmeans"`.
#' @param comparison Specify the type of contrasts or tests that should be
#' carried out. See also section *Comparison options* below for details.
#' * When `backend = "emmeans"`, can be one of `"pairwise"`, `"poly"`,
#'   `"consec"`, `"eff"`, `"del.eff"`, `"mean_chg"`, `"trt.vs.ctrl"`,
#'   `"dunnett"`, `"wtcon"` and some more. To test multiple hypotheses jointly
#'   (usually used for factorial designs), `comparison` can also be `"joint"`.
#'   See also `method` argument in [emmeans::contrast] and the
#'   `?emmeans::emmc-functions`.
#' * For `backend = "marginaleffects"`, can be a numeric value, vector, or
#'   matrix, a string equation specifying the hypothesis to test, a string
#'   naming the comparison method, a formula, or a function. For options not
#'   described below, see documentation of [marginaleffects::comparisons],
#'   [this website](https://marginaleffects.com/bonus/hypothesis.html) and
#'   section _Comparison options_ below.
#'   * String: One of `"pairwise"`, `"revpairwise"`, `"reference"`,
#'     `"sequential"`, `"meandev"` `"meanotherdev"`, `"poly"`, `"helmert"`, or
#'     `"trt_vs_ctrl"`. To test multiple hypotheses jointly (usually used for
#'     factorial designs), `comparison` can also be `"joint"` or `"omnibus"`. In
#'     this case, use the `test` argument to specify which test should be
#'     conducted: `"F"` (default) or `"Chi2"`, and use argument `null` to
#'     specify the null-hypothesis to test against. For `"omnibus"`, `null` is
#'     set to the overall mean or prevalence/proportion.
#'   * String: Special string options are `"inequality"`, `"inequality_ratio"`,
#'     and `"inequality_pairwise"`. `comparison = "inequality"` computes the
#'     marginal effect inequality summary of categorical predictors' overall
#'     effects, respectively, the comprehensive effect of an independent
#'     variable across all outcome categories of a nominal or ordinal dependent
#'     variable (also called *absolute inequality*, or total marginal effect,
#'     see _Mize and Han, 2025_). `"inequality_ratio"` computes the ratio of
#'     marginal effect inequality measures, also known as *relative inequality*.
#'     This is useful to compare the relative effects of different predictors on
#'     the dependent variable. It provides a measure of how much more or less
#'     inequality one predictor has compared to another.
#'     `comparison = "inequality_pairwise"` computes pairwise differences of
#'     absolute inequality measures, while `"inequality_ratio_pairwise"`
#'     computes pairwise differences of relative inequality measures (ratios).
#'     See an overview of applications in the related case study in the
#'     [vignettes](https://easystats.github.io/modelbased/articles/practical_inequalities.html).
#'   * String equation: To identify parameters from the output, either specify
#'     the term name, or `"b1"`, `"b2"` etc. to indicate rows, e.g.:`"hp = drat"`,
#'     `"b1 = b2"`, or `"b1 + b2 + b3 = 0"`.
#'   * Formula: A formula like `<comparison> ~ pairs | group`, where the
#'     left-hand side indicates the type of `<comparison>` (`difference` or
#'     `ratio`), the right-hand side determines the pairs of estimates to
#'     compare (`reference`, `pairwise`, `sequential`, `meandev`, etc., see
#'     string-options). Optionally, comparisons can be carried out within
#'     subsets by indicating the grouping variable after a vertical bar ( `|`).
#'     If the left-hand side is missing, it defaults to `difference` (i.e.
#'     `comparison = ~pairs | group` is identical to
#'     `comparison = difference ~ pairs | group`).
#'   * A custom function, e.g. `comparison = I(my_fun(x))`, or
#'     `<comparison> ~ I(my_fun(x)) | groups` (where `<comparison>` can be
#'     `difference` or `ratio`, or skipped).
#'   * If contrasts should be calculated (or grouped by) factors, `comparison`
#'     can also be a matrix that specifies factor contrasts (see 'Examples').
#' @param post_process Optional formula, character string or function (see
#' `comparison`), or a list of formulas, strings or functions, to process
#' subsequent, multi-step comparisons. After the initial comparison in
#' `comparison` is completed, the results are then post-processed using the
#' specified post-process tests. See 'Examples'.
#' @param effectsize Desired measure of standardized effect size, one of
#' `"emmeans"`, `"marginal"`, or `"boot"`. Default is `NULL`, i.e. no effect
#' size will be computed.
#' @param es_type Specifies the type of effect-size measure to estimate when
#' using `effectsize = "boot"`. One of `"unstandardized"`, `"cohens.d"`,
#' `"hedges.g"`, `"cohens.d.sigma"`, `"r"`, or `"akp.robust.d"`. See `effect.type`
#' argument of [`bootES::bootES()`] for details. If not specified, defaults to
#' `"cohens.d"`.
#' @param iterations The number of bootstrap resamples to perform.
#' @inheritParams estimate_means
#'
#' @inherit estimate_means details
#'
#' @inheritSection estimate_means Predictions and contrasts at meaningful values (data grids)
#'
#' @inheritSection estimate_means Predictions on different scales
#'
#' @section Comparison options:
#'
#' - `comparison = "pairwise"`: This method computes all possible unique
#'   differences between pairs of levels of the focal predictor. For example, if
#'   a factor has levels A, B, and C, it would compute A-B, A-C, and B-C.
#' - `comparison = "revpairwise"`: Like `"pairwise"`, but reverses the order
#'   of levels when comparing, e.g. B-A, C-A, and C-B.
#' - `comparison = "reference"`: This compares each level of the focal predictor
#'   to a specified reference level (by default, the first level). For example,
#'   if levels are A, B, C, and A is the reference, it computes B-A and C-A.
#' - `comparison = "sequential"`: This compares each level to the one
#'   immediately following it in the factor's order. For levels A, B, C, it
#'   would compute B-A and C-B.
#' - `comparison = "meandev"`: This contrasts each level's estimate against the
#'   grand mean of all estimates for the focal predictor.
#' - `comparison = "meanotherdev"`: Similar to `meandev`, but each level's
#'   estimate is compared against the mean of all *other* levels, excluding
#'   itself.
#' - `comparison = "poly"`: These are used for ordered categorical variables to
#'   test for linear, quadratic, cubic, etc., trends across the levels. They
#'   assume equal spacing between levels.
#' - `comparison = "helmert"`: Contrast 2nd level to the first, 3rd to the
#'   average of the first two, and so on. Each level (except the first) is
#'   compared to the mean of the preceding levels. For levels A, B, C, it would
#'   compute B-A and C-(A+B)/2.
#' - `comparison = "trt_vs_ctrl"`: This compares all levels (excluding the
#'   first, which is typically the control) against the first level. It's often
#'   used when comparing multiple treatment groups to a single control group.
#' - To test multiple hypotheses jointly (usually used for factorial designs),
#'   `comparison` can also be `"joint"`. This option runs sequential joint tests
#'   on the results of an initial pairwise comparison. To test for differences
#'   between groups from an global average, use `comparison = "omnibus"`, which
#'   conducts a global omnibus test. Use the `test` argument to specify which
#'   test should be conducted: `"F"` (default) or `"Chi2"`. Use the `null`
#'   argument to define a specific null-hypothesis to test against. For
#'   `"omnibus"`, `null` is set to the overall mean or prevalence/proportion.
#' - `comparison = "inequality"` computes the *absolute inequality* of groups,
#'   or in other words, the marginal effect inequality summary of categorical
#'   predictors' overall effects, respectively, the comprehensive effect of an
#'   independent variable across all outcome categories of a nominal or ordinal
#'   dependent variable (total marginal effect, see _Mize and Han, 2025_). The
#'   marginal effect inequality focuses on the heterogeneity of the effects of a
#'   categorical *independent* variable. It helps understand how the effect of
#'   the variable differs across its categories or levels. When the *dependent*
#'   variable is categorical (e.g., logistic, ordinal or multinomial
#'   regression), marginal effect inequality provides a holistic view of how an
#'   independent variable affects a nominal or ordinal *dependent* variable. It
#'   summarizes the overall impact (absolute inequality, or total marginal
#'   effects) across all possible outcome categories.
#' - `comparison = "inequality_ratio"` is comparable to
#'   `comparison = "inequality"`, but instead of calculating the absolute
#'   inequality, it computes the *relative inequality* of groups. This is useful
#'   to compare the relative effects of different predictors on the dependent
#'   variable. It provides a measure of how much more or less inequality one
#'   predictor has
#'   compared to another.
#' - `comparison = "inequality_pairwise"` computes pairwise differences of
#'   absolute inequality measures, while `"inequality_ratio_pairwise"` computes
#'   pairwise differences of relative inequality measures (ratios). Depending on
#'   the sign, this measure indicates which of the predictors has a stronger
#'   impact on the dependent variable in terms of inequalities.
#'
#' Examples for analysing inequalities are shown in the related
#' [vignette](https://easystats.github.io/modelbased/articles/practical_inequalities.html).
#' An introduction into pairwise comparisons and contrasts starts with this
#' [vignette](https://easystats.github.io/modelbased/articles/introduction_comparisons_1.html).
#'
#' @section Context Effects - contrasting average slopes:
#' Calculating contrasts between average slopes can tell us about the
#' "context" effects when modelling within- and between-effects. An example
#' for within- and between effects is described
#' [in this vignette](https://easystats.github.io/parameters/articles/demean.html).
#' A context effect describes the additional influence that the social or
#' regional environment (e.g., place of residence) has on an individual,
#' independent of their personal characteristics. It demonstrates that people
#' with identical individual circumstances (such as the same income) face
#' different opportunities or risks depending on the environment in which they
#' live. This can be achieved by specifying both numeric predictors in the
#' `contrast` argument, e.g. `contrast = c("x_within", "x_between")`. It is
#' also possible to stratify context effects at different levels of another
#' variable using `by`. If pairwise comparisons of context effects (i.e., the
#' pairwise comparisons of the difference of between within- and
#' between-effects, or the difference of average slopes) at different levels
#' of another variable are required, add that variable to the `contrast`
#' argument instead, e.g. `contrast = c("x_within", "x_between", "factor")`.
#' These contrasts can additionally be stratified by another variable using `by`
#' again, e.g. `contrast = c("x_within", "x_between", "factor1"), by = "factor2"`.
#' Note that when average slopes are contrasted, the `comparison` argument has
#' no effect and is always set to `"pairwise"`. See also 'Examples'.
#'
#' @section Effect Size:
#'
#' By default, `estimate_contrasts()` reports no standardized effect size on
#' purpose. Should one request one, some things are to keep in mind. As the
#' authors of *emmeans* write, "There is substantial disagreement among
#' practitioners on what is the appropriate sigma to use in computing effect
#' sizes; or, indeed, whether any effect-size measure is appropriate for some
#' situations. The user is completely responsible for specifying appropriate
#' parameters (or for failing to do so)."
#'
#' In particular, effect size method `"boot"` does not correct for covariates
#' in the model, so should probably only be used when there is just one
#' categorical predictor (with however many levels). Some believe that if there
#' are multiple predictors or any covariates, it is important to re-compute
#' sigma adding back in the response variance associated with the variables that
#' aren't part of the contrast.
#'
#' `effectsize = "emmeans"` uses [emmeans::eff_size] with
#' `sigma = stats::sigma(model)`, `edf = stats::df.residual(model)` and
#' `method = "identity"`. This standardizes using the MSE (sigma). Some believe
#' this works when the contrasts are the only predictors in the model, but not
#' when there are covariates. The response variance accounted for by the
#' covariates should not be removed from the SD used to standardize. Otherwise,
#' _d_ will be overestimated.
#'
#' `effectsize = "marginal"` uses the following formula to compute effect
#' size: `d_adj <- difference * (1- R2)/ sigma`. This standardizes
#' using the response SD with only the between-groups variance on the focal
#' factor/contrast removed. This allows for groups to be equated on their
#' covariates, but creates an appropriate scale for standardizing the response.
#'
#' `effectsize = "boot"` uses bootstrapping (defaults to a low value of
#' 200) through [bootES::bootES]. Adjusts for contrasts, but not for covariates.
#'
#' @references
#' - Mize, T., & Han, B. (2025). Inequality and Total Effect Summary Measures for
#'   Nominal and Ordinal Variables. Sociological Science, 12, 115–157.
#'   \doi{10.15195/v12.a7}
#'
#' - Montiel Olea, J. L., and Plagborg-Møller, M. (2019). Simultaneous
#'   confidence bands: Theory, implementation, and an application to SVARs.
#'   Journal of Applied Econometrics, 34(1), 1–17. \doi{10.1002/jae.2656}
#'
#' @examplesIf all(insight::check_if_installed(c("lme4", "emmeans", "marginaleffects", "parameters", "datawizard", "rstanarm"), quietly = TRUE))
#' \dontrun{
#' # Basic usage --------------------------------
#' # --------------------------------------------
#'
#' model <- lm(Sepal.Width ~ Species, data = iris)
#' estimate_contrasts(model)
#'
#' # Dealing with interactions
#' model <- lm(Sepal.Width ~ Species * Petal.Width, data = iris)
#'
#' # By default: selects first factor
#' estimate_contrasts(model)
#'
#' # Can also run contrasts between points of numeric, stratified by "Species"
#' estimate_contrasts(model, contrast = "Petal.Width", by = "Species")
#'
#' # Or both
#' estimate_contrasts(model, contrast = c("Species", "Petal.Width"), length = 2)
#'
#' # Or with custom specifications
#' estimate_contrasts(model, contrast = c("Species", "Petal.Width = c(1, 2)"))
#'
#' # Or modulate it
#' estimate_contrasts(model, by = "Petal.Width", length = 4)
#'
#' # Standardized differences
#' estimated <- estimate_contrasts(lm(Sepal.Width ~ Species, data = iris))
#' standardize(estimated)
#'
#' # contrasts of slopes ------------------------
#' # --------------------------------------------
#'
#' data(qol_cancer, package = "parameters")
#' qol_cancer$ID <- as.numeric(qol_cancer$ID)
#' qol_cancer$grp <- as.factor(ifelse(qol_cancer$ID < 100, "Group 1", "Group 2"))
#' model <- lm(QoL ~ time * education * grp, data = qol_cancer)
#'
#' # "time" only has integer values and few values, so it's treated like a factor
#' estimate_contrasts(model, "time", by = "education")
#'
#' # Setting `integer_as_continuous = TRUE` treats "time" as a continuous
#' # variable. This allows us to compare its average slope rather than making
#' # discrete pairwise comparisons at each time point.
#' estimate_contrasts(
#'   model,
#'   contrast = "time",
#'   by = "education",
#'   integer_as_continuous = TRUE
#' )
#'
#' # pairwise comparisons for multiple groups
#' estimate_contrasts(
#'   model,
#'   "time",
#'   by = c("education", "grp"),
#'   integer_as_continuous = TRUE
#' )
#'
#' # if we want pairwise comparisons only for one factor, but group by another,
#' # we need the formula specification and define the grouping variable after
#' # the vertical bar
#' estimate_contrasts(
#'   model,
#'   "time",
#'   by = c("education", "grp"),
#'   comparison = ~pairwise | grp,
#'   integer_as_continuous = TRUE
#' )
#'
#' # custom factor contrasts - contrasts the average effects of two levels
#' # against the remaining third level
#' # ---------------------------------------------------------------------
#'
#' data(puppy_love, package = "modelbased")
#' cond_tx <- cbind("no treatment" = c(1, 0, 0), "treatment" = c(0, 0.5, 0.5))
#' model <- lm(happiness ~ puppy_love * dose, data = puppy_love)
#' estimate_slopes(model, "puppy_love", by = "dose", comparison = cond_tx)
#'
#' # Note: for the emmeans-backend, we need to use `estimate_contrasts()` for
#' # the above example:
#' cond_tx <- list(`no treatment` = c(1, 0, 0), treatment = c(0, 0.5, 0.5))
#' estimate_contrasts(
#'   model,
#'   contrast = "puppy_love",
#'   by = "dose",
#'   comparison = cond_tx,
#'   backend = "emmeans"
#' )
#'
#' # Other models (mixed, Bayesian, ...) --------
#' # --------------------------------------------
#' data <- iris
#' data$Petal.Length_factor <- ifelse(data$Petal.Length < 4.2, "A", "B")
#'
#' model <- lme4::lmer(Sepal.Width ~ Species + (1 | Petal.Length_factor), data = data)
#' estimate_contrasts(model)
#'
#' data <- mtcars
#' data$cyl <- as.factor(data$cyl)
#' data$am <- as.factor(data$am)
#'
#' model <- rstanarm::stan_glm(mpg ~ cyl * wt, data = data, refresh = 0)
#' estimate_contrasts(model)
#' estimate_contrasts(model, by = "wt", length = 4)
#'
#' model <- rstanarm::stan_glm(
#'   Sepal.Width ~ Species + Petal.Width + Petal.Length,
#'   data = iris,
#'   refresh = 0
#' )
#' estimate_contrasts(model, by = "Petal.Length = [sd]", test = "bf")
#'
#' # Omnibus and Joint Tests ------------------------------------
#' #
#' # ------------------------------------------------------------
#' data(coffee_data, package = "modelbased")
#' m <- lm(alertness ~ time * coffee, data = coffee_data)
#' # joint test for simple effects of "time" for levels of "coffee".
#' estimate_contrasts(m, contrast = "time", by = "coffee", comparison = "joint")
#' # global omnibus test to test for differences between "time" levels
#' estimate_contrasts(m, contrast = "time", comparison = "omnibus")
#' # test against a different null-hypothesis
#' estimate_contrasts(m, contrast = "time", comparison = "omnibus", null = 15)
#'
#' # Context effects --------------------------------------------
#' # This is the difference of within- and between-effects, which
#' # typically are two average slopes that are compared. It is
#' # possible to calculate the context effect at different levels
#' # of another variable.
#' # ------------------------------------------------------------
#' data("qol_cancer", package = "parameters")
#' qol_cancer <- datawizard::demean(qol_cancer, select = "phq4", by = "ID")
#' model <- lme4::lmer(
#'   QoL ~ time * (phq4_within + phq4_between) + (1 + time | ID),
#'   data = qol_cancer
#' )
#'
#' # context effect (difference between within- and between-effect)
#' # at each time point - we calculate the contrast of two average slopes
#' # at different levels of "time"
#' estimate_contrasts(model, c("phq4_within", "phq4_between"), by = "time")
#'
#' # is the trend of the context effect across time points statistically
#' # significant? In this case, we just want the contrasts of the overall
#' # average slopes (not stratfied nor contrasted by time).
#' estimate_contrasts(model, c("phq4_within", "phq4_between"))
#'
#' # now we ask whether contexts effects are different for different educational
#' # levels. We now need to model a 3-way interaction between time, education
#' # and the centered phq4 variables.
#' model <- lme4::lmer(
#'   QoL ~ time * education * (phq4_within + phq4_between) + (1 + time | ID),
#'   data = qol_cancer
#' )
#'
#' # how do time trends of context effects differ between education levels?
#' estimate_contrasts(model, c("phq4_within", "phq4_between"), by = "education")
#'
#' # are differences in time trends of context effects statistically significant
#' # between education levels?
#' estimate_contrasts(model, c("phq4_within", "phq4_between", "education"))
#'
#' # Post-processing of multiple comparisons ---------------------
#' # Caution! Don't expect this example to be meaningful! # It is
#' # just to demonstrate the usage of the `post_process` argument.
#' # -------------------------------------------------------------
#' data("qol_cancer", package = "parameters")
#' model <- lme4::lmer(QoL ~ time * education + (1 + time | ID), data = qol_cancer)
#'
#' # contrasts (pairwise comparisons - the default) by timepoints
#' estimate_contrasts(model, "education=c('low', 'mid')", by = "time")
#'
#' # contrasts (pairwise comparisons, the default for `comparison`) by timepoints
#' # additionally, we compare the differences of these contrasts across timepoints
#' # against the reference time point (contrasts at times 2 and 3 against
#' # contrasts at time 1)
#' estimate_contrasts(model,
#'   contrast = "education=c('low', 'mid')",
#'   by = "time",
#'   post_process = ~reference
#' )
#'
#' # multiple post-processing steps - same as before, but calculates
#' # additional poly-contrasts applied to the reference contrasts (that
#' # were applied to the default pairwise comparisons of educational levels,
#' # grouped `by time`)
#' estimate_contrasts(model,
#'   contrast = "education=c('low', 'mid')",
#'   by = "time",
#'   post_process = list(~reference, ~poly)
#' )
#' }
#'
#' @return A data frame of estimated contrasts.
#' @export
estimate_contrasts <- function(model, ...) {
  UseMethod("estimate_contrasts")
}


#' @rdname estimate_contrasts
#' @export
estimate_contrasts.default <- function(
  model,
  contrast = NULL,
  by = NULL,
  predict = NULL,
  ci = 0.95,
  comparison = "pairwise",
  estimate = NULL,
  p_adjust = "none",
  transform = NULL,
  post_process = NULL,
  keep_iterations = FALSE,
  effectsize = NULL,
  iterations = 200,
  es_type = NULL,
  backend = NULL,
  verbose = TRUE,
  ...
) {
  # Process argument ---------------------------------------------------------
  # --------------------------------------------------------------------------

  # set defaults
  if (is.null(backend)) {
    backend <- getOption("modelbased_backend", "marginaleffects")
  }

  # validate input
  estimate <- .validate_estimate_arg(estimate)
  comparison <- .check_for_inequality_comparison(comparison)
  # Validate es_type usage
  if (is.null(effectsize) && !is.null(es_type)) {
    insight::format_error(
      "`es_type` can only be used when `effectsize` is specified. Currently `effectsize = NULL`."
    )
  }

  if (backend == "emmeans") {
    # Emmeans ----------------------------------------------------------------
    estimated <- get_emcontrasts(
      model,
      contrast = contrast,
      by = by,
      predict = predict,
      comparison = comparison,
      keep_iterations = keep_iterations,
      adjust = p_adjust,
      verbose = verbose,
      ...
    )
    out <- .format_emmeans_contrasts(model, estimated, ci, p_adjust, ...)
  } else {
    # Marginalmeans ----------------------------------------------------------
    estimated <- get_marginalcontrasts(
      model,
      contrast = contrast,
      by = by,
      predict = predict,
      comparison = comparison,
      p_adjust = p_adjust,
      ci = ci,
      estimate = estimate,
      transform = transform,
      post_process = post_process,
      keep_iterations = keep_iterations,
      verbose = verbose,
      ...
    )
    out <- format(estimated, model, p_adjust, comparison, ...)
  }

  # add effect size ----------------------------------------------------------
  if (!is.null(effectsize)) {
    out <- .estimate_contrasts_effectsize(
      model = model,
      estimated = estimated,
      contrasts_results = out,
      effectsize = effectsize,
      bootstraps = iterations,
      bootES_type = es_type,
      backend = backend
    )
  }

  # sanity check - did method return standard errors?
  .check_standard_errors(
    out,
    by = by,
    contrast = contrast,
    model = model,
    model_name = deparse(substitute(model)),
    verbose = verbose
  )

  # restore attributes later
  info <- attributes(estimated)
  type <- "contrasts"

  # Table formatting
  if (isTRUE(info$omnibus_test)) {
    suffix <- "Omnibus Test"
  } else if (isTRUE(info$joint_test)) {
    suffix <- "Joint Test"
  } else if (.is_inequality_comparison(comparison)) {
    suffix <- "Inequality Analysis"
    type <- "inequality"
  } else {
    suffix <- "Contrasts Analysis"
  }
  attr(out, "table_title") <- c(
    switch(
      estimate,
      specific = paste("Model-based", suffix),
      typical = paste("Marginal", suffix),
      average = paste("Averaged", suffix),
      population = paste("Counterfactual", suffix, "(G-computation)")
    ),
    "blue"
  )

  attr(out, "table_footer") <- .table_footer(
    out,
    by = info$contrast,
    type = type,
    model = model,
    info = info
  )

  # Add attributes
  attr(out, "model") <- model
  attr(out, "response") <- insight::find_response(model)
  attr(out, "ci") <- ci
  attr(out, "p_adjust") <- p_adjust
  attr(out, "backend") <- backend

  # we want to store the generic, not the ".default" method, in the call
  cl <- match.call()
  cl[[1L]] <- quote(estimate_contrasts)
  attr(out, "call") <- cl

  # add attributes from workhorse function
  attributes(out) <- utils::modifyList(attributes(out), info[.info_elements()])

  # Output
  class(out) <- c("estimate_contrasts", "see_estimate_contrasts", class(out))
  out
}
