#' Locally set options for maximal test reproducibility
#'
#' @description
#' `local_test_context()` is run automatically by `test_that()` but you may
#' want to run it yourself if you want to replicate test results interactively.
#' If run inside a function, the effects are automatically reversed when the
#' function exits; if running in the global environment, use
#' [withr::deferred_run()] to undo.
#'
#' `local_reproducible_output()` is run automatically by `test_that()` in the
#' 3rd edition. You might want to call it to override the the default settings
#' inside a test, if you want to test Unicode and coloured output or
#' non-standard width.
#'
#' @details
#' `local_test_context()` and `local_reproducible_output()`
#' set the following options with [withr::local_options()]:
#' * `cli.unicode` (default: `FALSE`) so that the cli package never generates unicode
#'   output (normally cli uses unicode on Linux/Mac but not Windows).
#'   Windows can't easily save unicode output to disk, so it must be set to
#'   false for consistency.
#' * `crayon.enabled` (default: `FALSE`) suppresses ANSI colours generated by the crayon
#'   package (normally colours are used if crayon detects that you're in a
#'   terminal that supports colour).
#' * `width` (default: 80) to control the width of printed output (usually this
#'   varies with the size of your console).
#'
#' In addition, `local_test_context()` sets the following options:
#'
#' * `cli.dynamic = FALSE` so that tests assume that they are not run in
#'   a dynamic console (i.e. one where you can move the cursor around).
#' * `lifecycle_verbosity = "warning"` so that every lifecycle problem always
#'   generates a warning (otherwise deprecated functions don't generate a
#'   warning every time).
#' * `OutDec = "."` so numbers always uses `.` as the decimal point
#'   (European users sometimes set `OutDec = ","`.)
#' * `rlang_interactive = FALSE` so that [rlang::is_interactive()] returns
#'   `FALSE`, and code that uses it assumes you're in a non-interactive
#'   environment.
#' * `useFancyQuotes = FALSE` so base R functions always use regular (straight)
#'   quotes (otherwise the default is locale dependent, see [sQuote()] for
#'   details).
#'
#' And modifies the following env vars:
#'
#' * Unsets `RSTUDIO`, which ensures that RStudio is never detected as running.
#' * Sets `TESTTHAT = "true"`, which ensures that [is_testing()] returns `TRUE`.
#' * Sets `LANGUAGE = "en"`, which ensures that no message translation occurs.
#'
#' Finally, it sets the collation locale to "C", which ensures that character
#' sorting the same regardless of system locale.
#'
#' @export
#' @keywords internal
#' @param .env Environment to use for scoping; expert use only.
#' @examples
#' local({
#'   local_test_context()
#'   cat(crayon::blue("Text will not be colored"))
#'   cat(cli::symbol$ellipsis)
#'   cat("\n")
#' })
local_test_context <- function(.env = parent.frame()) {
  withr::local_envvar(TESTTHAT = "true", .local_envir = .env)
  if (edition_get() >= 3) {
    local_reproducible_output(.env = .env)
  }
}

#' @export
#' @param width Value of the `"width"` option.
#' @param crayon Value of the `"crayon.enabled"` option.
#' @param unicode Value of the `"cli.unicode"` option.
#'   The test is skipped if `` l10n_info()$`UTF-8` `` is `FALSE`.
#' @rdname local_test_context
#' @examples
#'
#' ellipsis <- cli::symbol$ellipsis
#' test_that("test ellipsis", {
#'   expect_equal(ellipsis, cli::symbol$ellipsis)
#'
#'   local_reproducible_output(unicode = TRUE)
#'   expect_equal(ellipsis, cli::symbol$ellipsis)
#' })
local_reproducible_output <- function(width = 80,
                                      crayon = FALSE,
                                      unicode = FALSE,
                                      .env = parent.frame()) {

  if (unicode) {
    # If you force unicode display, you _must_ skip the test on non-utf8
    # locales; otherwise it's guaranteed to fail
    skip_if(!l10n_info()$`UTF-8`, "non utf8 locale")
  }

  local_width(width = width, .env = .env)
  withr::local_options(
    crayon.enabled = crayon,
    cli.dynamic = FALSE,
    cli.unicode = unicode,
    useFancyQuotes = FALSE,
    lifecycle_verbosity = "warning",
    OutDec = ".",
    rlang_interactive = FALSE,
    .local_envir = .env
  )
  withr::local_envvar(
    RSTUDIO = NA,
    LANGUAGE = "en",
    .local_envir = .env
  )
  withr::local_collate("C", .local_envir = .env)
}

waldo_compare <- function(x, y, ..., x_arg = "x", y_arg = "y") {

  reporter <- get_reporter()
  if (!is.null(reporter)) {
    # Need to very carefully isolate this change to this function - can not set
    # in expectation functions because part of expectation handling bubbles
    # up through calling handlers, which are run before on.exit()
    reporter$local_user_output()
  }

  waldo::compare(x, y,..., x_arg = x_arg, y_arg = y_arg)
}

local_width <- function(width = 80, .env = parent.frame()) {
  withr::local_options(width = width, cli.width = width, .local_envir = .env)
  withr::local_envvar(RSTUDIO_CONSOLE_WIDTH = width, .local_envir = .env)
}


#' Locally set test directory options
#'
#' For expert use only.
#'
#' @param path Path to directory of files
#' @param package Optional package name, if known.
#' @export
#' @keywords internal
local_test_directory <- function(path, package = NULL, .env = parent.frame()) {
  # Set edition before changing working directory in case path is relative
  local_edition(find_edition(path, package), .env = .env)

  withr::local_dir(path, .local_envir = .env)
  withr::local_envvar(
    R_TESTS = "",
    TESTTHAT = "true",
    TESTTHAT_PKG = package,
    .local_envir = .env
  )
}

local_interactive_reporter <- function(.env = parent.frame()) {
  # Definitely not on CRAN
  withr::local_envvar(NOT_CRAN = "true", .local_envir = .env)
  withr::local_options(testthat_interactive = TRUE, .local_envir = .env)

  # Use edition from working directory
  local_edition(find_edition("."), .env = .env)

  # Use StopReporter
  reporter <- StopReporter$new(stop_reporter = FALSE)
  old <- set_reporter(reporter)
  withr::defer(set_reporter(old), envir = .env)

  reporter
}
