#' Check distribution of simulated quantile residuals
#'
#' `check_residuals()` checks generalized linear (mixed) models for uniformity
#' of randomized quantile residuals, which can be used to identify typical model
#' misspecification problems, such as over/underdispersion, zero-inflation, and
#' residual spatial and temporal autocorrelation.
#'
#' @param x A supported model object or an object returned by
#' [`simulate_residuals()`] or [`DHARMa::simulateResiduals()`].
#' @param alternative A character string specifying the alternative hypothesis.
#' Can be one of `"two.sided"`, `"less"`, or `"greater"`. See
#' [`stats::ks.test()`] for details.
#' @param distribution The distribution to compare the residuals against.
#' Can be (a) a character value giving a cumulative distribution function
#' (for example, `"punif"` (default) or `"pnorm"`), (b) a cumulative distribution
#' function itself (for example, `punif` or `pnorm`), or (c) a numeric vector
#' of values.
#' @param ... Passed down to [`stats::ks.test()`].
#'
#' @details Simulated quantile residuals are generated by simulating a series of
#' values from a fitted model for each case, comparing the observed response
#' values to these simulations, and computing the empirical quantile of the
#' observed value in the distribution of simulated values. When the model is
#' correctly-specified, these quantile residuals will follow a *uniform* (flat)
#' distribution. `check_residuals()` tests the distribution of the quantile
#' residuals against the uniform distribution using a Kolmogorov-Smirnov test.
#' Essentially, comparing quantile residuals to the uniform distribution tests
#' whether the observed response values deviate from model expectations
#' (i.e., simulated values). In this sense, `check_residuals()` is similar to
#' posterior predictive checks with [`check_predictions()`].
#'
#' There is a `plot()` method to visualize the distribution of quantile residuals
#' using a Q-Q plot. This plot can be interpreted in the same way as a Q-Q plot
#' for normality of residuals in linear regression.
#'
#' If desired, a different theoretical distribution or a vector of numeric
#' values can be tested against using the `distribution` argument.
#'
#' @inheritSection simulate_residuals Tests based on simulated residuals
#'
#' @seealso [`simulate_residuals()`], [`check_zeroinflation()`],
#' [`check_overdispersion()`] and [`check_predictions()`]. See also
#' [`see::plot.see_performance_simres()`] for options to customize the plot.
#'
#' @return The p-value of the test statistics.
#'
#' @examplesIf require("DHARMa")
#' dat <- DHARMa::createData(sampleSize = 100, overdispersion = 0.5, family = poisson())
#' m <- glm(observedResponse ~ Environment1, family = poisson(), data = dat)
#' res <- simulate_residuals(m)
#' check_residuals(res)
#'
#' @export
check_residuals <- function(x, ...) {
  UseMethod("check_residuals")
}

#' @rdname check_residuals
#' @export
check_residuals.default <- function(
  x,
  alternative = "two.sided",
  distribution = "punif",
  ...
) {
  if (insight::is_model(x)) {
    check_residuals(simulate_residuals(x, ...), alternative = alternative)
  } else {
    insight::format_error(
      "`check_residuals()` only works with objects supported by `simulate_residuals()` or `DHARMa::simulateResiduals()`."
    ) # nolint
  }
}


#' @export
check_residuals.fa <- function(x, ...) {
  check_normality(x, ...)
}

#' @export
check_residuals.principal <- check_residuals.fa

#' @export
check_residuals.parameters_efa <- check_residuals.fa

#' @export
check_residuals.omega <- check_residuals.fa

#' @export
check_residuals.item_omega <- function(x, ...) {
  model <- attributes(x)$model
  check_residuals(model, ...)
}


#' @export
check_residuals.performance_simres <- function(
  x,
  alternative = "two.sided",
  distribution = "punif",
  ...
) {
  alternative <- insight::validate_argument(
    alternative,
    c("two.sided", "less", "greater")
  )
  ts_test <- suppressWarnings(
    stats::ks.test(
      stats::residuals(x),
      distribution,
      alternative = alternative,
      ...
    )
  )

  p.val <- ts_test$p.value

  attr(p.val, "data") <- x
  attr(p.val, "object_name") <- insight::safe_deparse_symbol(substitute(x))
  class(p.val) <- unique(c("check_residuals", "see_check_residuals", class(p.val)))

  p.val
}

#' @export
check_residuals.DHARMa <- check_residuals.performance_simres


# methods ------------------------------

#' @export
print.check_residuals <- function(x, ...) {
  pstring <- insight::format_p(x)

  if (x < 0.05) {
    insight::print_color(
      sprintf(
        "Warning: Non-uniformity of simulated residuals detected (%s).\n",
        pstring
      ),
      "red"
    )
  } else {
    insight::print_color(
      sprintf(
        "OK: Simulated residuals appear as uniformly distributed (%s).\n",
        pstring
      ),
      "green"
    )
  }

  invisible(x)
}

#' @export
plot.check_residuals <- function(x, ...) {
  insight::check_if_installed("see", "for residual plots")
  NextMethod()
}
