#' @title Generate Bootstrap Estimates
#'
#' @description Get a list of the output
#' of [lavaan::sem()] or [lmhelprs::many_lm()]
#' and generate
#' bootstrap estimates of model
#' parameters.
#'
#' @details
#' The function [gen_boot()]
#' simply calls [manymome::do_boot()]
#' on each output of
#' [lavaan::sem()] or [lmhelprs::many_lm()]
#' in `fit_all`. The
#' simulated
#' estimates can then be used to test
#' effects such as indirect effects,
#' usually by functions from the
#' `manymome` package, such as
#' [manymome::indirect_effect()].
#'
#' # The role of `gen_boot()`
#'
#' This function is used by the
#' all-in-one function [power4test()].
#' Users usually do not call this
#' function directly, though
#' developers can use this function to
#' customize the workflow of the
#' power analysis.
#'
#' @seealso See [power4test()] for
#' the all-in-one function that uses
#' this function.
#'
#' @param fit_all The output of
#' [fit_model()] or an object of the
#' class `fit_out`.
#'
#' @param R The number of replications
#' to generate the bootstrap estimates
#' for each fit output.
#'
#' @param ... Optional arguments to be
#' passed to [manymome::do_boot()] when
#' generating the bootstrap estimates.
#'
#' @param iseed The seed for the random
#' number generator. Default is `NULL`
#' and the seed is not changed.
#'
#' @param parallel If `TRUE`, parallel
#' processing will be used to generate
#' bootstrap estimates for the
#' fit outputs. Default is `FALSE`.
#'
#' @param progress If `TRUE`, the progress
#' will be displayed.
#' Default is `FALSE.
#'
#' @param ncores The number of CPU
#' cores to use if parallel processing
#' is used.
#'
#' @param compute_implied_stats Whether
#' implied statistics are computed
#' in each bootstrap samples. Usually
#' not needed and so default to `FALSE`
#'
#' @param cl A cluster, such as one created
#' by [parallel::makeCluster()]. If `NULL`,
#' a cluster will be created, but will be
#' stopped on exit. If set to an existing
#' cluster, it will not be stopped when
#' the function exits; users need to
#' stop it manually.
#'
#' @return
#' A `boot_list` object, which is a list
#' of the output of [manymome::do_boot()].
#'
#' @examples
#'
#' # Specify the population model
#'
#' mod <-
#' "m ~ x
#'  y ~ m + x"
#'
#' # Specify the effect sizes (population parameter values)
#'
#' es <-
#' "
#' y ~ m: m
#' m ~ x: m
#' y ~ x: n
#' "
#'
#' # Generate several simulated datasets
#'
#' data_all <- sim_data(nrep = 2,
#'                      model = mod,
#'                      pop_es = es,
#'                      n = 50,
#'                      iseed = 1234)
#'
#' # Fit the population model to each datasets
#'
#' fit_all <- fit_model(data_all)
#'
#' # Generate bootstrap estimates for each replication
#'
#' boot_all <- gen_boot(fit_all,
#'                      R = 10,
#'                      iseed = 4567)
#' boot_all
#'
#'
#' @export
#'
gen_boot <- function(fit_all,
                     R = 100,
                     ...,
                     iseed = NULL,
                     parallel = FALSE,
                     progress = FALSE,
                     ncores = max(1, parallel::detectCores(logical = FALSE) - 1),
                     compute_implied_stats = FALSE,
                     cl = NULL) {
  # This check can be removed after manymome is updated on CRAN.
  # The version requirement should be added to DESCRIPTION later.
  manymome_implied <- (utils::packageVersion("manymome") >= "0.2.7.1")
  if (isFALSE(manymome_implied)) {
    stop("manymome 0.2.7.1 or later is required. ",
         "If not available from CRAN, can be installed from ",
         "GitHub: remotes::install_github('sfcheung/manymome')")
  }
  out <- do_FUN(X = fit_all,
                FUN = gen_boot_i,
                R = R,
                ...,
                compute_implied_stats = compute_implied_stats,
                iseed = iseed,
                parallel = parallel,
                progress = progress,
                ncores = ncores,
                cl = cl)
  class(out) <- c("boot_list", class(out))
  return(out)
}

#' @noRd
gen_boot_i <- function(fit_i,
                       R = 100,
                       seed = NULL,
                       compute_implied_stats = FALSE,
                       ...) {
  boot_out <- tryCatch(manymome::do_boot(fit = fit_i,
                                         R = R,
                                         ...,
                                         compute_implied_stats = compute_implied_stats,
                                         parallel = FALSE,
                                         progress = FALSE),
                      error = function(e) e)
  boot_out
}
