
#' Plan package installations
#'
#' `renv::plan()` resolves the packages required by a project and generates
#' a lockfile, without installing any packages. This allows you to see what
#' `renv::restore()` would do before actually performing the installation.
#'
#' Because packages are not installed, the generated lockfile is based on
#' package metadata from the configured repositories (i.e. the `PACKAGES`
#' file). This means the lockfile may contain only a subset of the fields
#' that would be present in a lockfile generated by [renv::snapshot()],
#' which reads the full `DESCRIPTION` from installed packages. The
#' generated lockfile is still suitable for use with [renv::restore()].
#'
#' @inheritParams renv-params
#'
#' @param packages The packages to be included in the plan. When `NULL`
#'   (the default), all packages currently used in the project will be
#'   included, as determined by [renv::dependencies()].
#'
#' @param lockfile The path where the generated lockfile should be written.
#'   Use `NULL` to skip writing the lockfile.
#'
#' @return The generated lockfile, invisibly.
#'
#' @examples
#' \dontrun{
#'
#' # plan the packages required by the current project
#' renv::plan()
#'
#' # plan a specific set of packages
#' renv::plan(packages = c("dplyr", "ggplot2"))
#'
#' }
#' @export
plan <- function(packages     = NULL,
                 ...,
                 dependencies = NULL,
                 lockfile     = paths$lockfile(project = project),
                 project      = NULL)
{
  renv_consent_check()
  renv_scope_error_handler()
  renv_dots_check(...)

  project <- renv_project_resolve(project)
  renv_project_lock(project = project)

  # resolve the set of packages to plan for
  requested <- packages
  packages <- packages %||% renv_plan_packages(project)

  # expand dependency fields
  fields <- renv_dependencies_fields(dependencies, project = project)

  # get available packages database
  dbs <- available_packages(type = "source")
  if (is.null(dbs))
    stop("no package repositories are available")

  db <- renv_available_packages_flatten(dbs)

  # warn about user-requested packages not available in repositories
  renv_checkout_report(requested, db)

  # resolve the full recursive dependency tree
  packages <- renv_checkout_resolve(packages, db, fields, project = project)

  # build enriched lockfile records from available packages metadata
  records <- renv_checkout_records(packages, db)

  # assemble the lockfile
  new <- renv_lockfile_init(project)
  new$Packages <- records
  new <- renv_lockfile_fini(new, project)
  class(new) <- "renv_lockfile"

  # compare against existing lockfile if one exists
  renv_plan_report(new, fields)

  # write the lockfile
  if (!is.null(lockfile)) {
    ensure_parent_directory(lockfile)
    renv_lockfile_write(new, file = lockfile)
  }

  invisible(new)

}

renv_plan_packages <- function(project) {
  renv_dependencies_impl(
    project,
    field = "Package",
    dev = TRUE
  )
}

renv_plan_report <- function(new, fields) {

  records <- renv_lockfile_records(new)
  if (empty(records)) {
    writef("- No packages found for this project.")
    return(invisible())
  }

  writef("The following dependency tree will be encoded in the lockfile:\n")
  roots <- renv_graph_roots(records, fields)
  renv_graph_print(records, roots, fields)

}
