utils::globalVariables(c(
  ".", "..apCachedCols", "..cols", "..cols3", "..colsHere", "..colsToKeep", "..keepCols1",
  ".GRP", "Account", "Additional_repositories", "availableVersionOKthisOne",
  "binOrSrc", "bothDepAndOrig", "Branch", "comp", "depOrOrig",
  "DESCFile", "EqualsDoesntViolate", "forceInstall", "getOptions",
  "GitSubFolder", "GTDoesntViolate", "hasEqualsAndInequals", "hasGTAndInequals",
  "hasHEAD", "hasInequality", "hasSubFolder", "hasVers", "hasVersionsToCompare",
  "haveLocal", "i.localFile", "i.VersionOnRepos", "ineq", "installed",
  "installedVersionOK", "installSafeGroups", "isBinaryInstall",
  "isEquals", "isGT", "keep44", "keep55", "keepBasedOnRedundantInequalities",
  "keepCols3", "keepCols4", "keepCols5", "keepForUpdate", "LibPath", "loadOrder",
  "localFile", "mayNeedSwitchToSrc", "needInstall", "needKeep", "newLocalFile",
  "oppositeInequals", "ord", "ordB", "PackageUrl", "parentPackage", "parentVersionQueried",
  "repo", "Repo", "Repository", "SHAonGH", "SHAonLocal", "verbose",
  "Version", "VersionOK", "VersionOnRepos", "versionSpec", "versionToKeep",
  "violation", "violation2", "violationsDoubleInequals", "whArchive"
))

#' Rerun-safe (idempotent) install and load packages, optionally with specific versions
#'
#' This is an "all in one" function that will run `install.packages` for CRAN
#' and [GitHub](https://github.com/) packages and will install specific versions
#' of each package if versions are specified either via an (in)equality
#' (e.g., `"glue (>=1.6.2)"` or `"glue (==1.6.2)"` for an exact version) or with a
#' `packageVersionFile`. If `require = TRUE`, the default, the function will
#' then run `require` on all named packages that satisfy their version requirements.
#' If packages are already installed (`packages` supplied), and their optional
#' version numbers are satisfied, then the "install" component will be skipped.
#'
#' @return `Require` is intended to replace `base::require`, thus it returns a
#' logical, named vector indicating whether the named packages have been loaded.
#' Because `Require` also has the ability to install packages, a return value of
#' `FALSE` does not mean that it did not install correctly; rather, it means it
#' did not attach with `require`, which could be because it did not install
#' correctly, or also because e.g., `require = FALSE`.
#'
#' `standAlone` will either put the `Require`d packages and their dependencies
#' *all* within the `libPaths` (if `TRUE`) or if `FALSE` will only install
#' packages and their dependencies that are otherwise not installed in
#' `.libPaths()[1]`, i.e., the current active R package directory. Any packages
#' or dependencies that are not yet installed will be installed in `libPaths`.
#'
#'
#' @section GitHub Package: Follows `remotes::install_github` standard. As with
#'   `remotes::install_github`, it is not possible to specify a past version of
#'   a GitHub package unless that version is a tag or the user passes the SHA
#'   that had that package version. Similarly, if a developer does a local
#'   install e.g., via `pkgload::install`, of an active project, this package
#'   will not be able know of the GitHub state, and thus `pkgSnapshot` will not
#'   be able to recover this state as there is no SHA associated with a local
#'   installation. Use `Require` (or `remotes::install_github`) to create a
#'   record of the GitHub state.
#'
#' @section Package Snapshots: To build a snapshot of the desired packages and
#'   their versions, first run `Require` with all packages, then `pkgSnapshot`.
#'   If a `libPaths` is used, it must be used in both functions.
#'
#' @section Mutual Dependencies: This function works best if all required
#'   packages are called within one `Require` call, as all dependencies can be
#'   identified together, and all package versions will be addressed (if there
#'   are no conflicts), allowing a call to `pkgSnapshot()` to take a snapshot or
#'   "record" of the current collection of packages and versions.
#'
#' @section Local Cache of Packages: When installing new packages, `Require`
#'   will put all source and binary files in an R-version specific subfolder of
#'   `getOption("Require.cachePkgDir")` whose default is `RPackageCache()`,
#'   meaning *cache packages locally in a project-independent location*, and
#'   will reuse them if needed. To turn off this feature, set
#'   `options("Require.cachePkgDir" = FALSE)`.
#'
#' @note For advanced use and diagnosis, the user can set `verbose = TRUE` or
#' `1` or `2` (or via `options("Require.verbose")`). This will attach an
#' attribute `attr(obj, "Require")` to the output of this function.
#'
#' @param install Logical or "force". If `FALSE`, this will not try to install
#'   anything. If `"force"`, then it will force installation of requested
#'   packages, mimicking a call to e.g., `install.packages`. If `TRUE`, the
#'   default, then this function will try to install any missing packages or
#'   dependencies.
#' @param require Logical or character string. If `TRUE`, the default, then the
#'   function will attempt to call `require` on all requested `packages`,
#'   possibly after they are installed. If a character string, then it will only
#'   call `require` on those specific packages (i.e., it will install the ones
#'   listed in `packages`, but load the packages listed in `require`)
#' @param packages Either a character vector of packages to install via
#'   `install.packages`, then load (i.e., with `library`), or, for convenience,
#'   a vector or list (using `c` or `list`) of unquoted package names to install
#'   and/or load (as in `require`, but vectorized). Passing vectors of names may
#'   not work in all cases, so user should confirm before relying on this behaviour
#'   in operational code.
#'   In the case of a GitHub package, it
#'   will be assumed that the name of the repository is the name of the package.
#'   If this is not the case, then pass a *named* character vector here, where the
#'   names are the package names that could be different than the GitHub
#'   repository name.
#'   As a convenience for copy-pasting long lists, any character element that
#'   contains newlines is split into one package per line, with whitespace
#'   trimmed and blank or `#`-prefixed lines dropped. For example,
#'   `Require("\n  dplyr\n  # ggplot2\n  PredictiveEcology/LandR@development\n")`
#'   is equivalent to
#'   `Require(c("dplyr", "PredictiveEcology/LandR@development"))`.
#'   Alternatively, an unquoted `{...}` block is accepted, with each line
#'   treated as one package spec, e.g.
#'   `Require({ dplyr; lme4; PredictiveEcology/LandR@development })`.
#'   Note that R's parser strips comments inside `{...}` before this function
#'   runs, so to omit a package delete the line (or comment it out -- both
#'   have the same effect). Version constraints such as `pkg (>= 1.0)` do
#'   not parse inside `{...}` and must use the quoted string form.
#' @param packageVersionFile  Character string of a file name or logical. If
#'   `TRUE`, then this function will load the default file,
#'   `getOption("Require.packageVersionFile")`. If this argument is provided,
#'   then this will override any packages passed to `packages`. By default,
#'   `Require` will attempt to resolve dependency violations (i.e., if this
#'   `packageVersionFile` specifies a version of a package that violates
#'   the dependency specification of another package). If a user wishes to attempt
#'   to install the `packageVersionFile` without assessing the dependencies,
#'   set `dependencies = FALSE`.
#' @param libPaths The library path (or libraries) where all packages should be
#'   installed, and looked for to load (i.e., call `library`). This can be used
#'   to create isolated, stand alone package installations, if used with
#'   `standAlone = TRUE`. Currently, the path supplied here will be prepended to
#'   `.libPaths()` (temporarily during this call) to `Require` if
#'   `standAlone = FALSE` or will set (temporarily) `.libPaths()` to
#'   `c(libPaths, tail(libPaths(), 1)` to keep base packages.
#' @param repos The remote repository (e.g., a CRAN mirror), passed to either
#'   `install.packages`, `install_github` or `installVersions`.
#'   **When `options(Require.usePak = TRUE)`:** `repos` is added to pak's repository
#'   list via `options(repos)`. However, pak always includes CRAN and Bioconductor as
#'   built-in defaults regardless of this setting -- `repos` can only *add* sources,
#'   it cannot prevent pak from also searching CRAN. This differs from the default
#'   (`usePak = FALSE`) behaviour where `repos` strictly controls which repositories
#'   are used. Use `pak::cache_clean()` to clear pak's download cache if needed.
#' @param install_githubArgs Deprecated. Values passed here are merged with
#'   `install.packagesArgs`, with the `install.packagesArgs` taking precedence
#'   if conflicting.
#' @param install.packagesArgs List of optional named arguments, passed to
#'   `install.packages`. Default is only `--no-multi-arch`, meaning that only
#'   the current architecture will be built and installed (e.g., 64 bit, not 32 bit,
#'   in many cases).
#' @param standAlone Logical. If `TRUE`, all packages will be installed to and
#'   loaded from the `libPaths` only. NOTE: If `TRUE`, THIS WILL CHANGE THE
#'   USER'S `.libPaths()`, similar to e.g., the `checkpoint` package. If
#'   `FALSE`, then `libPath` will be prepended to `.libPaths()` during the
#'   `Require` call, resulting in shared packages, i.e., it will include the
#'   user's default package folder(s). This can be create dramatically faster
#'   installs if the user has a substantial number of the packages already in
#'   their personal library. Default `FALSE` to minimize package installing.
#' @param purge Logical. Should all caches be purged? Default is
#'   `getOption("Require.purge", FALSE)`. There is a lot of internal caching of
#'   results throughout the `Require` package. These help with speed and reduce
#'   calls to internet sources. However, sometimes these caches must be purged.
#'   The cached values are renewed when found to be too old, with the age limit.
#'   This maximum age can be set in seconds with the environment variable
#'   `R_AVAILABLE_PACKAGES_CACHE_CONTROL_MAX_AGE`, or if unset, defaults to 3600
#'   (one hour -- see [`utils::available.packages`]).
#'
#'   Internally, there are calls to `available.packages`.
#' @param verbose Numeric or logical indicating how verbose should the function
#'   be. If -1 or -2, then as little verbosity as possible. If 0 or FALSE,
#'   then minimal outputs; if `1` or TRUE, more outputs; `2` even more. NOTE: in
#'   `Require` function, when `verbose >= 2`, also returns details as if
#'   `returnDetails = TRUE` (for backwards compatibility).
#' @param returnDetails Logical. If `TRUE` the return object will have an
#'   attribute: `attr(.., "Require")` which has lots of information about the
#'   processes of the installs.
#' @param type See `utils::install.packages`
#' @param typeExplicit Logical. Whether `type` was supplied by the caller
#'   rather than defaulted from `getOption("pkgType")`. Defaults to
#'   `!missing(type)` and should not normally be set by hand. It exists because
#'   `getOption("pkgType")` is `"source"` on Linux, so the value of `type`
#'   alone cannot say whether source-only builds were actually asked for; only
#'   an explicit request pins pak to source.
#' @param upgrade When `FALSE`, the default, will only upgrade a package when the
#'   version on in the local library is not adequate for the version requirements
#'   of the `packages`. Note: for convenience, `update`
#'   can be used for this argument.
#' @param ... Passed to `install.packages`. Good candidates are e.g., `type` or
#'   `dependencies`. This can be used with `install_githubArgs` or
#'   `install.packageArgs` which give individual options for those 2 internal
#'   function calls.
#'
#' @export
#' @importFrom data.table as.data.table data.table is.data.table rbindlist set setDT
#' @importFrom data.table  :=  .I .SD setnames setorderv
#' @importFrom utils available.packages capture.output compareVersion
#' @importFrom utils install.packages packageVersion
#'
#' @examplesIf Require:::.runLongExamples()
#' opts <- Require:::.setupExample()
#'
#' library(Require)
#' getCRANrepos(ind = 1)
#' Require("utils") # analogous to require(stats), but it checks for
#' #   pkg dependencies, and installs them, if missing
#'
#' # unquoted version
#' Require(c(tools, utils))
#'
#' # Install in a new local library (libPaths)
#' tempPkgFolder <- file.path(tempdir(), "Require/Packages")
#' # use standAlone, means it will put it in libPaths, even if it already exists
#' #   in another local library (e.g., personal library)
#' Install("crayon", libPaths = tempPkgFolder, standAlone = TRUE)
#'
#' # Mutual dependencies, only installs once -- e.g., cli
#' tempPkgFolder <- file.path(tempdir(), "Require/Packages")
#' Install(c("cli", "R6"), libPaths = tempPkgFolder, standAlone = TRUE)
#'
#' # Mutual dependencies, only installs once -- e.g., rlang
#' tempPkgFolder <- file.path(tempdir(), "Require/Packages")
#' Install(c("rlang", "ellipsis"), libPaths = tempPkgFolder, standAlone = TRUE)
#'
#' #####################################################################################
#' # Isolated projects -- Use a project folder and pass to libPaths or set .libPaths() #
#' #####################################################################################
#' # GitHub packages
#' if (requireNamespace("gitcreds", quietly = TRUE)) {
#'   # if (is(try(gitcreds::gitcreds_get(), silent = TRUE), "gitcreds")) {
#'   ProjectPackageFolder <- file.path(tempdir(), "Require/ProjectA")
#'   if (requireNamespace("curl")) {
#'     Require("PredictiveEcology/fpCompare@development",
#'       libPaths = ProjectPackageFolder,
#'     )
#'   }
#'
#'   # No install because it is there already
#'   Install("PredictiveEcology/fpCompare@development",
#'     libPaths = ProjectPackageFolder,
#'   ) # the latest version on GitHub
#'
#'   ############################################################################
#'   # Mixing and matching GitHub, CRAN, with and without version numbering
#'   ############################################################################
#'   pkgs <- c(
#'     "remotes (<=2.4.1)", # old version
#'     "digest (>= 0.6.28)", # recent version
#'     "PredictiveEcology/fpCompare@a0260b8476b06628bba0ae73af3430cce9620ca0" # exact version
#'   )
#'   Require::Require(pkgs, libPaths = ProjectPackageFolder)
#'   # }
#' }
#' Require:::.cleanup(opts)
#'
Require <- function(packages,
                    packageVersionFile,
                    libPaths, # nolint
                    install_githubArgs = list(),
                    install.packagesArgs = list(INSTALL_opts = "--no-multiarch"),
                    standAlone = getOption("Require.standAlone", FALSE),
                    install = getOption("Require.install", TRUE),
                    require = getOption("Require.require", TRUE),
                    repos = getOption("repos"),
                    purge = getOption("Require.purge", FALSE),
                    verbose = getOption("Require.verbose", FALSE),
                    type = getOption("pkgType"),
                    typeExplicit = !missing(type),
                    upgrade = FALSE,
                    returnDetails = FALSE,
                    ...) {

  stRequire <- Sys.time()
  if (is.null(require)) require <- FALSE
  assign("hasGHP", NULL, envir = pkgEnv()) # clear GITHUB_PAT message; only once per Require session
  opts <- setNcpus()
  on.exit(
    {
      options(opts)
      checkAutomaticOfflineMode() # This will turn off offlineMode if it had been turned on automatically
    },
    add = TRUE
  )

  dots <- list(...)
  if (!is.null(dots$update)) {
    upgrade <- dots$update
  }
  dealWithCache(purge)
  purge <- FALSE

  if (length(install.packagesArgs))
    if (is.null(names(install.packagesArgs)))
      stop("install.packagesArgs must be a list with *named* elements, e.g., INSTALL_opts")

  verboseSpecial <- verbose < 5
  install.packagesArgs <- modifyList2(list(quiet = !(verbose >= 1 && verboseSpecial)), install.packagesArgs,
                                      dots,
                                      keep.null = TRUE
  )
  install_githubArgs <- modifyList2(list(quiet = !(verbose >= 0)  && verboseSpecial), install_githubArgs,
                                    dots,
                                    keep.null = TRUE
  )
  install.packagesArgs <- modifyList2(
    install.packagesArgs,
    list(destdir = ".", repos = repos, type = types())
  )

  libPaths <- doLibPaths(libPaths, standAlone = standAlone)
  libPaths <- checkLibPaths(libPaths = libPaths, exact = TRUE, ...)

  # Make sure pak is reachable on .libPaths() BEFORE any downstream code path
  # calls pak; if it is not, install it fresh into the project lib. pak runs
  # its work in a callr subprocess that does its own loadNamespace("pak")
  # against the inherited .libPaths(), so any entry will serve -- but a pak
  # loaded only in this session will not. Never file-copy pak into place:
  # its embedded callr/processx helper executables do not survive a
  # file-by-file copy on Windows and the next pak call dies with
  #   `Native call to processx_exec failed: Command '' not found`.
  #
  # Gate on libPaths[1] being part of the session-wide .libPaths() so this
  # only fires for *real* project libs. Ephemeral install-target tempdirs
  # passed via Require::Install(pkg, libPaths = some_tempdir, standAlone =
  # TRUE) -- common in tests and one-off installs -- are NOT project libs;
  # treating them as such (a) wastes install time per call and (b) triggers
  # an unloadNamespace("pak") cascade that interacts badly with packages
  # the caller is in the middle of installing/unloading.
  if (.isSessionLibPath(libPaths[1]))
    ensurePakInProjectLib(libPaths[1], repos = repos, verbose = verbose)

  doDeps <- if (!is.null(list(...)$dependencies)) list(...)$dependencies else NA
  which <- whichToDILES(doDeps)

  if (!missing(packageVersionFile)) {
    if (isFALSE(packageVersionFile)) {
      messageVerbose(NoPkgsSupplied, verbose = verbose, verboseLevel = 1)
    }
    pkgSnapshotOut <- doPkgSnapshot(packageVersionFile, purge, libPaths = libPaths,
                                    install_githubArgs, install.packagesArgs, standAlone,
                                    type = type, verbose = verbose, returnDetails = returnDetails, ...
    )
    return(invisible(pkgSnapshotOut))
  }
  if (missing(packages)) {
    messageVerbose(NoPkgsSupplied, verbose = verbose, verboseLevel = 1)
    return(invisible(NULL))
  }

  packagesSubstituted <- substitute(packages) # can be c(xx), list(xx), "hi", a$b is a call, but it is likely already evaluated
  packages <- substitutePackages(packagesSubstituted, envir = parent.frame())
  packages <- parseMultiLinePackages(packages)

  hasInitSlash <- grepl("^\\\"", packages)
  if (any(hasInitSlash))
    packages[hasInitSlash] <- gsub("\\\"", "", packages[hasInitSlash])

  # noRemotes: rewrite GitHub specs (account/repo@branch) to bare names so they
  # resolve from `repos` (e.g., r-universe binaries) rather than being cloned
  # and built from GitHub source. Avoids git auth + Rtools for end users (e.g.,
  # workshops). Version constraints are preserved, so `repos` must carry a
  # version satisfying them. See ?RequireOptions.
  if (isTRUE(getOption("Require.noRemotes", FALSE)))
    packages <- stripGitHubToRepos(packages, verbose = verbose)

  # Proceed to evaluate install and load need if there are any packages
  packagesOrig <- packages
  if (NROW(packages)) {
    repos <- getCRANrepos(repos, ind = 1)

    basePkgsToLoad <- packages[packages %in% .basePkgs]

    # `install = FALSE` means: don't reach CRAN/GitHub to install or update --
    # just load whatever is already installed. Dependency resolution (via pak
    # or pkgDep) hits CRAN, which on Windows + RStudio triggers an SSL warning
    # via .rs.downloadFile(CRAN_mirrors.csv), and on a multi-package mix the
    # pak path emits a noisy "switching to per-package resolution" Note even
    # when there's nothing to do. Short-circuit to the no-deps pkgDT here so
    # the downstream installed/require pipeline runs without any network call.
    skipDepResolution <- isFALSE(install)

    if (skipDepResolution) {
      pkgDT <- toPkgDTFull(packages)
    } else if (getOption("Require.usePak", FALSE)) {
      # Pass repos to pak via options(repos): pak's remote() subprocess copies
      # getOption("repos") into the subprocess environment. IMPORTANT LIMITATION:
      # pak::repo_get() always includes CRAN + Bioconductor as built-in defaults
      # regardless of options(repos). options(repos) only *adds* repos to pak's list;
      # it cannot prevent pak from using CRAN. This differs from install.packages().
      opts <- options(repos = repos)
      on.exit(options(opts), add = TRUE)

      ## Only install a message-condition handler when we actually need to
       ## suppress output. Installing a calling handler that doesn't muffle
       ## (the previous "tee to log file at verbose>1" path) sat in cli's
       ## condition chain and broke cli's progress redraw heuristic — every
       ## tick rendered as a fresh line instead of overwriting in place. cli
       ## decides between dynamic redraw and static emission partly based on
       ## whether an upstream handler will consume cliMessage; a no-op
       ## handler that just teed to a file flipped that decision. Skipping
       ## the wrap entirely at verbose >= 1 lets cli see the unobstructed
       ## chain and use its own redraw handler.
       if (verbose < 1) {
         withCallingHandlers(
           pkgDT <- pakDepsToPkgDT(packages, which = which, libPaths = libPaths,
                                    standAlone = standAlone, verbose = verbose,
                                    purge = purge, install = install, type = type,
                                    typeExplicit = typeExplicit),
           message = function(m) invokeRestart("muffleMessage")
         )
       } else {
         pkgDT <- pakDepsToPkgDT(packages, which = which, libPaths = libPaths,
                                  standAlone = standAlone, verbose = verbose,
                                  purge = purge, install = install, type = type,
                                  typeExplicit = typeExplicit)
       }
    } else if (!skipDepResolution) {
      if (length(which)) {
        # "Rdpack"     "S7"         "rbibutils"  "reformulas"
        deps <- pkgDep(packages, simplify = FALSE,
                       purge = purge, libPaths = libPaths, recursive = TRUE,
                       which = which, type = type, verbose = verbose, repos = repos,
                       Additional_repositories = TRUE
        )
        deps4 <- parsePackageFullname(data.table::copy(deps))
        deps3 <- confirmEqualsDontViolateInequalitiesThenTrim(data.table::copy(deps4))

        deps2 <- rbindlist(deps3$deps, fill = TRUE, use.names = TRUE)
        deps2 <- cleanUpRecursivePkgVersionIssues(deps2, verbose)

        # If there were archives in pkgDep, it had to go get them, so it has them locally, don't want to waste them
        if (!is.null(deps[["localFile"]])) {
          deps2 <- deps2[deps[, c("packageFullName", "newLocalFile", "localFile")], on = "packageFullName"]
        }
        deps <- unique(deps2)
        pkgDT <- deps
      } else {
        pkgDT <- toPkgDTFull(packages)
      }
    }

    # Shared version-priority pipeline (both pak and non-pak branches converge here)
    if (NROW(pkgDT)) {
      pkgDT <- checkHEAD(pkgDT)
      pkgDT$packageFullName <- cleanPkgs(pkgDT$packageFullName) # this should do e.g., pemisc (== 0.0.3.9004) and pemisc (==0.0.3.9004)

      pkgDT <- confirmEqualsDontViolateInequalitiesThenTrim(pkgDT)
      pkgDT <- trimRedundancies(pkgDT)

      pkgDT <- updatePackagesWithNames(pkgDT, packages)
      ## Always record loadOrder, even when require = FALSE. loadOrder is the
      ## marker for "user explicitly asked for this package" -- whichToInstall
      ## relies on it (R/Require2.R: askedByUser logic) to mark force-install
      ## rows as needInstall = .txtInstall. Gating this on `require` meant
      ## Install(pkg, install = "force") -- which calls Require(require=FALSE)
      ## -- silently skipped pak install entirely. doLoads at L1093 already
      ## handles both "loadOrder set" and "loadOrder NULL" cases.
      pkgDT <- recordLoadOrder(packages, pkgDT)
      if (!is.null(pkgDT[["Version"]]))
        setnames(pkgDT, old = "Version", new = "VersionOnRepos")
      pkgDT <- installedVers(pkgDT, libPaths = libPaths, standAlone = standAlone)
      if (isTRUE(upgrade)) {
        pkgDT <- getVersionOnRepos(pkgDT, repos = repos, purge = purge, libPaths = libPaths)
        if (any(pkgDT[["VersionOnRepos"]] != pkgDT[["Version"]], na.rm = TRUE)) {
          sameVersion <- compareVersion2(pkgDT[["VersionOnRepos"]], pkgDT[["Version"]], "==")
          pkgDT[!sameVersion, comp := compareVersion2(VersionOnRepos, Version, ">=")]
          pkgDT[!sameVersion & comp %in% TRUE,
                `:=`(Version = NA, installed = FALSE, versionSpec = VersionOnRepos)]
          set(pkgDT, NULL, "comp", NULL)
        }
      }
      pkgDT <- dealWithStandAlone(pkgDT, libPaths, standAlone)
      pkgDT <- whichToInstall(pkgDT, install, verbose, libPaths = libPaths)

      # If a candidate is already loaded in this session with a version that
      # satisfies the constraint, skip reinstall -- both to avoid pak's
      # "namespace 'X' is imported by 'Y' so cannot be unloaded" failure mode
      # and because there is no work to do.  Honoured even for HEAD-checked
      # GitHub refs: the user's intent in pinning a `(>= X.Y.Z)` constraint
      # is the version, not whichever commit happens to be at HEAD right now.
      if (!identical(install, "force"))
        pkgDT <- useLoadedIfSufficient(pkgDT, libPaths = libPaths,
                                       standAlone = standAlone, verbose = verbose)

      # Deal with "force" installs
      set(pkgDT, NULL, "forceInstall", FALSE)
      if (install %in% "force") {
        wh <- which(pkgDT$Package %in% extractPkgName(packages))
        set(pkgDT, wh, "installedVersionOK", FALSE)
        set(pkgDT, wh, "forceInstall", TRUE)
        ## whichToInstall's force-branch identifies user-requested packages
        ## via loadOrder, but recordLoadOrder is gated on `require != FALSE`,
        ## so Install(pkg, install = "force") (which calls Require with
        ## require = FALSE) left needInstall = .txtDontInstall and
        ## pakInstallFiltered returned silently without calling pak::pak().
        ## Set needInstall directly here, where `packages` is in scope, so
        ## the install fires regardless of the require/loadOrder state.
        set(pkgDT, wh, "needInstall", .txtInstall)
      }

      needInstalls <- (any(pkgDT$needInstall %in% .txtInstall) && (isTRUE(install))) || install %in% "force"
      if (needInstalls) {
        if (getOption("Require.usePak", FALSE)) {
          ## Cache shortcut: if every package we'd install is already in
          ## pak's download cache, skip pak's online resolver and install
          ## from the cache directly. Avoids pak's metadata refresh (which
          ## can stall on TCP timeouts when the network is down) and is
          ## consistent with Require's "don't unnecessarily check for
          ## updates" philosophy -- the same rule already applied to
          ## installed packages. Skip the shortcut when the user
          ## explicitly asked for `install = "force"` or set
          ## `purge = TRUE`; those signal "ignore the cache".
          # Use the bespoke offline cache-install shortcut (pakOfflineInstall) ONLY
          # in genuine offline mode. It used to ALSO fire whenever every package was
          # already in pak's download cache (allInPakCache) -- a "skip the metadata
          # refresh" optimization -- but that routes a perfectly ONLINE install into
          # the bespoke exact-pin batch, which hands pak every dep-tree node as a
          # `pkg@version` ref and self-conflicts in pak's resolver
          # ("openssl@2.4.2: Conflicts with openssl@2.4.2"), collapsing to the slow
          # per-ref one-at-a-time fallback. When online, prefer pak's own
          # resolve+install (pakInstallFiltered): it uses pak's download cache
          # anyway, orders builds natively, and carries the retry/error-handling
          # catches (pakRetryLoop, pakErrorHandling). pakOfflineInstall remains for
          # true offlineMode, and pakInstallFiltered's recovery block below still
          # falls back to it if the network turns out to be down.
          useCacheShortcut <- isTRUE(getOption("Require.offlineMode"))
          if (useCacheShortcut) {
            if (!isTRUE(getOption("Require.offlineMode"))) {
              messageVerbose("All requested packages are in the pak ",
                             "download cache; installing from cache ",
                             "(no metadata refresh, no network)",
                             verbose = verbose, verboseLevel = 1)
            }
            pkgDT <- pakOfflineInstall(pkgDT, libPaths = libPaths, verbose = verbose)
          } else {
            pkgDT <- pakInstallFiltered(pkgDT, libPaths = libPaths, repos = repos,
                                        standAlone = standAlone, verbose = verbose,
                                        forceUpgrade = identical(install, "force"),
                                        type = type, typeExplicit = typeExplicit)
            # Invalidate the dep-tree cache: installed state changed, so the next
            # call should re-resolve rather than use a stale cached result.
            pakDepsCacheInvalidate(pkgsForPak = trimVersionNumber(HEADtoNone(pkgDT$packageFullName)),
                                   wh   = whichToDILES(doDeps),
                                   repos = repos,
                                   type = type)
            ## Recovery: if pakInstallFiltered left any rows flagged
            ## .txtCouldNotBeInstalled, probe internet once. If missing,
            ## switch to offlineMode and retry those rows via the pak
            ## download cache. This pays the 2-second probe only on the
            ## sad path (install failed); the happy path stays untouched.
            ## The auto-set offlineMode flag is cleared by
            ## checkAutomaticOfflineMode() on Require()'s on.exit so the
            ## user's explicit setting is preserved.
            failedRows <- which(pkgDT$installResult %in% .txtCouldNotBeInstalled)
            if (length(failedRows)) {
              setOfflineModeTRUE(verbose = verbose, force = TRUE)
              if (isTRUE(getOption("Require.offlineMode"))) {
                messageVerbose("Install failed and no internet detected; ",
                               "retrying via local pak download cache",
                               verbose = verbose, verboseLevel = 1)
                set(pkgDT, failedRows, "needInstall", .txtInstall)
                set(pkgDT, failedRows, "installResult", NA_character_)
                pkgDT <- pakOfflineInstall(pkgDT, libPaths = libPaths,
                                            verbose = verbose)
              }
            }
          }
        } else {
          pkgDT <- doInstalls(pkgDT,
                              repos = repos, purge = purge, libPaths = libPaths,
                              install.packagesArgs = install.packagesArgs,
                              type = type, returnDetails = returnDetails,
                              verbose = verbose
          )
        }
      } else {
        messageVerbose("No packages to install/update", verbose = verbose)
      }
    }
    if (length(basePkgsToLoad) && !isFALSE(require)) {
      pkgDTBase <- toPkgDT(basePkgsToLoad)
      set(pkgDTBase, NULL, c("loadOrder", "installedVersionOK"), list(1L, TRUE))
      if (exists("pkgDT", inherits = FALSE)) {
        pkgDTBase <- rbindlist(list(pkgDT, pkgDTBase), use.names = TRUE, fill = TRUE)
      }
      pkgDT <- pkgDTBase
    }

    pkgDT <- needToRestartR(pkgDT)
    pkgDT <- flagRestartForLoadedInsufficient(pkgDT)
    whRestartNeeded <- which(grepl("restart", pkgDT$installResult))
    if  (length(whRestartNeeded)) {
      warning(.txtPleaseRestart, "; ", paste(pkgDT[whRestartNeeded]$Package, collapse = ", "),
              singularPlural(c(" was", " were"), l = whRestartNeeded), " already loaded and ",
              paste(pkgDT[whRestartNeeded]$packageFullName, collapse = ", "),
              singularPlural(c(" was", " were"), l = whRestartNeeded), " installed.")

    }

    # Recovery (pak path): a user-requested package may have been removed from
    # pkgDT by step-3b inside pakDepsToPkgDT (pak's CRAN resolution can't satisfy
    # a dev-version constraint) even though the installed version satisfies it.
    # If such a package is absent from pkgDT but is installed at a satisfying
    # version, add a minimal row back so doLoads() will call require() for it.
    if (getOption("Require.usePak", FALSE) && exists("pkgDT", inherits = FALSE)) {
      userPkgFull   <- packages[!extractPkgName(packages) %in% .basePkgs]
      missingFromDT <- setdiff(extractPkgName(userPkgFull), pkgDT$Package)
      if (length(missingFromDT)) {
        ipAll <- tryCatch({
          ipRaw <- installed.packages(lib.loc = libPaths)
          setNames(ipRaw[, "Version"], ipRaw[, "Package"])
        }, error = function(e) character(0))
        # For each missing package, check installed version against user constraint
        missingPkgFull <- userPkgFull[extractPkgName(userPkgFull) %in% missingFromDT]
        missingPkgDT   <- toPkgDTFull(missingPkgFull)
        missingPkgDT   <- confirmEqualsDontViolateInequalitiesThenTrim(missingPkgDT)
        missingPkgDT   <- trimRedundancies(missingPkgDT)
        recoverable <- vapply(seq_len(NROW(missingPkgDT)), function(i) {
          pkg     <- missingPkgDT$Package[i]
          instVer <- ipAll[pkg]
          if (is.na(instVer) || !nzchar(instVer)) return(FALSE)
          ineq <- missingPkgDT$inequality[i]
          vsp  <- missingPkgDT$versionSpec[i]
          if (is.na(ineq) || !nzchar(ineq)) return(TRUE)   # no constraint -> any installed version OK
          isTRUE(compareVersion2(instVer, vsp, ineq))
        }, logical(1))
        if (any(recoverable)) {
          recoverDT <- missingPkgDT[recoverable]
          recoverPkgs <- recoverDT$Package
          set(recoverDT, NULL, "Version",            ipAll[recoverPkgs])
          set(recoverDT, NULL, "installed",          TRUE)
          set(recoverDT, NULL, "installedVersionOK", TRUE)
          set(recoverDT, NULL, "needInstall",        .txtDontInstall)
          # Assign loadOrder so doLoads() will call require() for these packages.
          # Start numbering after the max existing loadOrder to avoid collisions.
          maxLO <- if (!is.null(pkgDT$loadOrder) && any(!is.na(pkgDT$loadOrder)))
            max(pkgDT$loadOrder, na.rm = TRUE) else 0L
          set(recoverDT, NULL, "loadOrder", seq(maxLO + 1L, maxLO + NROW(recoverDT)))
          pkgDT <- rbindlist(list(pkgDT, recoverDT), fill = TRUE, use.names = TRUE)
          messageVerbose(
            "pak path: recovering ", length(recoverPkgs),
            " package(s) absent from dep tree but installed at satisfying version: ",
            paste(recoverPkgs, collapse = ", "),
            verbose = verbose, verboseLevel = 1
          )
        }
        # Remaining truly-missing packages (not installed / wrong version) -> diagnostic
        trulyMissing <- missingFromDT[!missingFromDT %in% (if (any(recoverable)) recoverDT$Package else character(0))]
        if (length(trulyMissing) && isTRUE(verbose >= 1))
          messageVerbose("pak path: user-requested packages absent from dep tree and not ",
                         "loadable (not installed or constraint not satisfied): ",
                         paste(trulyMissing, collapse = ", "),
                         verbose = verbose, verboseLevel = 1)
      }
    }

    # This only has access to "trimRedundancies", so it cannot know the right answer about which was loaded or not
    out <- doLoads(require, pkgDT, libPaths = libPaths, verbose = verbose)

    packagesDT <- matchWithOriginalPackages(pkgDT, packages)
    out <- packagesDT$require
    names(out) <- packagesDT[["packageFullName"]]

    if (verbose >= 2 || returnDetails %in% TRUE) {
      if (is.null(require)) {
        out <- character()
      }
      attr(out, "Require") <- pkgDT[]
    }
  } else {
    out <- logical()
  }

  et <- Sys.time()
  et <- difftime(et, stRequire)
  numPacksInstalled <- NROW(pkgDT$installed[(pkgDT$installed %in% TRUE | is.na(pkgDT$installed)) &
                                              (pkgDT$needInstall %in% .txtInstall | is.na(pkgDT$installed)) &
                                              (pkgDT$needInstall %in% .txtInstall & pkgDT$installResult %in% "OK")])
  if (numPacksInstalled > 0)
    messageVerbose(paste0("Installed ", numPacksInstalled,
                          " packages in "),
                   round(et, 1), " ", attr(et, "units"), verbose = verbose)

  noneAv <- pkgDT$installResult %in% .txtNoneAvailable
  if (isTRUE(any(noneAv))) {
    warning(messageCantInstallNoVersion(
      paste(pkgDT[["packageFullName"]][which(noneAv)], collapse = ", ")), call. = FALSE)
  }

  noInternet <- pkgDT$installResult %in% .txtNoInternetNoLocalCantInstall
  if (isTRUE(any(noInternet))) {
    warning(messageCantInstallNoInternet(
      paste(pkgDT[["packageFullName"]][which(noInternet)], collapse = ", ")), call. = FALSE)
  }

  return(invisible(out))
}

rbindlistRecursive <- function(ll) {
  if (is(ll, "list")) {
    ll <- lapply(ll, rbindlistRecursive)
    ll <- rbindlist(ll, fill = TRUE, use.names = TRUE, idcol = FALSE)
  }
  ll
}

rbindlistNULL <- function(ll, ...) {
  theNulls <- sapply(ll, is.null)
  if (sum(theNulls) == 1)
    return(ll[[which(!theNulls)]])
  else
    rbindlist(ll, ...)
}


#' @importFrom sys exec_wait exec_background
build <- function(Package, VersionOnRepos, verbose, quiet) {
  if (nchar(Sys.which("R")) > 0) {
    messageVerbose("building package (R CMD build)",
                   verbose = verbose, verboseLevel = 2
    )
    # internal <- !interactive()
    extras <- c(
      "--no-resave-data", "--no-manual",
      "--no-build-vignettes"
    )
    Rpath1 <- Sys.getenv("R_HOME")
    Rpath <- file.path(Rpath1, "bin/R") # need to use Path https://stat.ethz.ch/pipermail/r-devel/2018-February/075507.html
    filePat <- paste0(Package, "_", VersionOnRepos, ".tar.gz")
    logFile <- tempfile2(fileext = ".log")
    out1 <- Map(pack = Package, function(pack) {
      # R CMD build can't handle DESCRIPTION with blank lines -- this is like
      #   https://github.com/r-lib/devtools/pull/439
      descFile <- file.path(Package, "DESCRIPTION")
      a <- readLines(descFile)
      if (any(nchar(a)))
        writeLines(a[nzchar(a)], descFile)
      # cmdLine <- paste("CMD build ", pack, paste(extras, collapse = " "))
      cmdLine <- c("CMD", "build", pack, extras)
      if (getOption("Require.installPackagesSys", 0L)) {

        pid <- sys::exec_wait(
          Rpath, I(cmdLine), # std_out = con, std_err = con
          std_out = function(x) {
            mess <- rawToChar(x)

            msgStdOutForBuild(mess, logFile, verbose)
            # pkg <- extractPkgNameFromWarning(mess)
            # cat(blue(mess), file = logFile, append = TRUE)
            # appendLF <- endsWith(mess, "\n") %in% FALSE
            # if (verbose >= 2) {
            #   messageVerbose(blue(mess), verbose = verbose, appendLF = appendLF)
            # } else {
            #   if (grepl("building", mess)) {
            #     mess <- gsub(".+(building.+)", "\\1", mess)
            #     messageVerbose(blue(mess), appendLF = appendLF)
            #   }
            # }

            #
            invisible()
          },
          std_err = function(x) {
            mess <- rawToChar(x)
            msgStdErrForBuild(mess, logFile, verbose)
            # cat(greyLight(mess), file = logFile, append = TRUE)
            # appendLF <- endsWith(mess, "\n") %in% FALSE
            # if (verbose <= 1) {
            #   appendLF <- endsWith(mess, "\n") %in% FALSE
            #   messageVerbose(greyLight(mess), verbose = verbose, appendLF = appendLF)
            # } else {
            #   messageVerbose(greyLight(mess), verbose = verbose, appendLF = appendLF)
            # }

            invisible()
          }
        )
        tools::pskill(pid)
      } else {
        ver <- ifelse(installPackageVerbose(verbose, verboseLevel = 1), "", FALSE)
        system2(Rpath, cmdLine,
                stdout = ver,
                stderr = ver)
      }
    })
    messageVerbose("\b\b ... Built!",
                   verbose = verbose, verboseLevel = 1
    )
  } else {
    stop("Can't install packages this way because R is not on the search path")
  }
}


installAll <- function(toInstall, repos = getOptions("repos"), purge = FALSE, install.packagesArgs,
                       numPackages, numGroups, startTime, type = type, returnDetails,
                       tmpdir = tempdir(), libPaths,
                       verbose = getOption("Require.verbose")) {

  messageForInstall(startTime, toInstall, numPackages, verbose, numGroups)
  type <- if (isWindows() || isMacOS()) {
    # "binary"
    unique(c("source", "binary")[toInstall$isBinaryInstall + 1])
  } else {
    "source"
  }

  install.packagesArgs$INSTALL_opts <- unique(c(install.packagesArgs$INSTALL_opts, "--build"))

  # if (isWindows() & getRversion() < "4.2") { # older windows can't update packages before removing them
  #   toInstall <- rmPackageFirst(toInstall, libPaths, verbose)
  # }

  # for (i in 1:2) {
  (ap <- availablePackagesOverride(toInstall, repos, purge, type = type, verbose = verbose))

  if (is(ap, "try-error")) {
    browserDeveloper("Error 9566")
  }

  # "repos" is interesting -- must be NULL, not just unspecified, for Local; must be unspecified or specified for Archive & CRAN
  #  This means that we can't get parallel installs for GitHub or Cache
  install.packagesArgs <- modifyList2(install.packagesArgs, list(destdir = NULL), keep.null = TRUE)
  ipa <- modifyList2(install.packagesArgs,
                     list(pkgs = toInstall[["Package"]], available = ap, type = type, dependencies = FALSE),
                     keep.null = TRUE
  )

  rcf <- getOption("Require.cloneFrom")
  if (isTRUE(is.character(rcf))) {
    ipa <- clonePackages(rcf, ipa, libPaths = libPaths, verbose)
  }

  if (NROW(ipa$available)) {
    ipa$destdir <- tmpdir
    ipa$lib <- libPaths[1]

    on.exit({
      logFile <- if (exists("toInstallOut", inherits = FALSE)) toInstallOut else NULL
      rmErroredPkgInstalls(logFile = logFile, toInstall, verbose)
    },
    add = TRUE)
    tries <- seq(1, 3) # may need several rounds of this for failed compiled pkgs
    for (attempt in tries) {
      toInstallOut <- try(withCallingHandlers(
        installPackagesWithQuiet(ipa, verbose = verbose),
        warning = function(w) {
          try(messagesAboutWarnings(w, toInstall, returnDetails = returnDetails,
                                tmpdir = tmpdir, verbose = verbose)) # changes to toInstall are by reference; so they are in the return below
          invokeRestart("muffleWarning") # muffle them because if they were necessary, they were redone in `messagesAboutWarnings`
        }
      ))
      if (!is.null(toInstallOut)) {
        errorCond <- attr(toInstallOut, "condition")
        rl <- try(readLines(toInstallOut), silent = TRUE)
        if (length(rl)) {
          ipaNext <- checkCompileFailedThenInstallBinary(rl, ipa, toInstall, verbose)
          if (!identical(ipa, ipaNext)) {
            ipa <- ipaNext;
            next
          }
          # Detect "namespace 'X' Y is being loaded, but >= Z is required" -- the new
          # version of the package being installed needs a newer dep than is currently
          # installed.  Install that dep now, then retry the failing package.
          nsLines <- grep(
            "namespace '[^']+' [^ ]+ is being loaded, but >= [^ ]+ is required",
            rl, value = TRUE)
          if (length(nsLines)) {
            reqPkg <- gsub(".*namespace '([^']+)' .+ is being loaded.*", "\\1", nsLines[1])
            reqVer <- gsub(".*is being loaded, but >= ([^ ]+) is required.*", "\\1", nsLines[1])
            pkgSpec <- paste0(reqPkg, " (>= ", reqVer, ")")
            messageVerbose("  ", pkgSpec, " needs upgrading; installing before retry ...",
                           verbose = verbose)
            try(Install(pkgSpec, verbose = verbose - 1L))
            next  # retry the original package now that the dep is satisfied
          }
        }
      }

      if (!is(toInstallOut, "try-error") || !is.null(errorCond)) {
        break
      }
      ipa <- recoverFromFail(toInstallOut, toInstall, ipa,
                             attempt = attempt, tries = tries,
                             repos = repos, tmpdir = tmpdir, libPaths = libPaths,
                             verbose, returnDetails)
    }
  }
  toInstall
}

doInstalls <- function(pkgDT, repos, purge, libPaths, install.packagesArgs,
                       type = getOption("pkgType"), returnDetails, verbose) {
  tmpdir <- tempdir3() # do all downloads and installs to here; then copy to Cache, if used

  pkgDTList <- split(pkgDT, by = c("needInstall"))
  if (NROW(pkgDTList[[.txtInstall]])) {
    pkgInstall <- pkgDTList[[.txtInstall]] # pointer

    on.exit(
      {
        # this copies any tar.gz files to the package cache; works even if partial install.packages
        tmpdirPkgs <- file.path(tempdir(), "downloaded_packages") # from CRAN installs
        if (length(dir(c(tmpdir, tmpdirPkgs)))) {
          copyBuiltToCache(pkgInstall, tmpdirs = c(tmpdir, tmpdirPkgs))
        }
        suppressWarnings(try(postInstallDESCRIPTIONMods(pkgInstall, libPaths), silent = TRUE)) # CRAN is read only after pkgs installed
        # unlink(tmpdir, recursive = TRUE)
      },
      add = TRUE
    )

    # if (!getOption("Require.cachePkgDir") %in% FALSE)
    #  wd <- cachePkgDir()
    # else
    wd <- tmpdir

    # problem... building in cache dir has nice feature that zip is there immediately ...
    #   so when say 100 pkgs are being installed from source, the zips are built along
    #   the way, so if it crashes after 90 installs, the zips are in the Cache
    #   BUT -- when a GH package gets built here, it has to get the "simple" name without SHA
    #   but that means that it will collide with the same package from CRAN with same version
    #   number ... BUT, we may want to keep them separate e.g.,
    #  fpCompare-a0260b8476b06628bba0ae73af3430cce9620ca0_0.2.4.tar.gz
    #  is built to fpCompare_0.2.4.zip ... so GH packages should be build in tmpdir, so they can
    #  be renamed to fpCompare-a0260b8476b06628bba0ae73af3430cce9620ca0_0.2.4.zip afterwards,
    #  moved to cache, without colliding with a CRAN package fpCompare_0.2.4.zip ... obviously
    #  these could be the same, based on version number, but we shouldn't assume that

    origDir <- setwd(wd) # this is where zip gets built
    on.exit(setwd(origDir), add = TRUE)

    # needInstall can switch from "install" to "dontInstall" inside because of "HEAD", which gets compared to current
    pkgInstall <- doDownloads(pkgInstall,
                              repos = repos, purge = purge, verbose = verbose,
                              install.packagesArgs = install.packagesArgs, libPaths = libPaths,
                              type = type, tmpdir = tmpdir
    )

    pkgInstallList <- split(pkgInstall, by = c("needInstall"))
    for (i in setdiff(names(pkgInstallList), .txtDontInstall))
      pkgDTList[[i]] <- pkgInstallList[[i]]
    if (!is.null(pkgInstallList[[.txtDontInstall]])) {
      pkgDTList[[.txtDontInstall]] <- rbindlist(list(pkgDTList[[.txtDontInstall]], pkgInstallList[[.txtDontInstall]]),
                                                fill = TRUE, use.names = TRUE)
      if (getOption("Require.offlineMode"))
        pkgDTList[[.txtInstall]] <- pkgDTList[[.txtInstall]][!pkgDTList[[.txtDontInstall]], on = "packageFullName"]
    }
    if (NROW(pkgDTList[[.txtInstall]])) {
      pkgInstallList <- split(pkgInstall, by = "needInstall") # There are now ones that can't be installed b/c .txtNoneAvailable
      pkgInstall <- pkgInstallList[[.txtInstall]]

      # This next one is a warning at the end of installation. Removed this message Sep 27, 2024
      # if (!is.null(pkgInstallList[[.txtNoneAvailable]])) {
      #   messageVerbose(messageCantInstallNoVersion(pkgInstallList[[.txtNoneAvailable]][["packageFullName"]]),
      #                  verbose = verbose, verboseLevel = 1)
      # }
      if (!is.null(pkgInstallList[[.txtShaUnchangedNoInstall]])) {
        messageVerbose(.txtShaUnchangedNoInstall, ": ", pkgInstallList[[.txtShaUnchangedNoInstall]][["packageFullName"]],
                       verbose = verbose, verboseLevel = 1)
      }

      if (!is.null(pkgInstall)) {
        pkgInstall[, isBinaryInstall := isBinary(localFile, needRepoCheck = FALSE)] # filename-based
        pkgInstall[localFile %in% useRepository, isBinaryInstall := isBinaryCRANRepo(Repository)] # repository-based
        if (isTRUE(any(duplicated(pkgInstall$Package)))) {
          pkgInstall <- updateReposForSrcPkgs(pkgInstall, verbose = verbose)
        }

        startTime <- Sys.time()

        # The install
        pkgInstall[, installSafeGroups := 1L]
        if (isWindows() || isMacOS()) {
          pkgInstall[, installSafeGroups := (isBinaryInstall %in% FALSE) + 1L]
          pkgInstall <- updateInstallSafeGroups(pkgInstall)
        }

        maxGroup <- max(pkgInstall[["installSafeGroups"]])
        numPackages <- NROW(pkgInstall)
        setorderv(pkgInstall, c("installSafeGroups", "Package"))
        pkgInstall[, installOrder := seq(.N)]

        if (isWindows() || isMacOS())
          toInstallList <- split(pkgInstall, by = "installSafeGroups")
        else
          toInstallList <- list(pkgInstall)

        # The INSTALL
        toInstallList <-
          Map(
            toInstall = toInstallList,
            MoreArgs = list(
              repos = repos, purge = purge,
              install.packagesArgs = install.packagesArgs, numPackages = numPackages,
              numGroups = maxGroup, startTime = startTime, type = type,
              returnDetails = returnDetails,
              libPaths = libPaths,
              tmpdir = tmpdir, verbose = verbose
            ),
            installAll
          )
        pkgInstallTmp <- rbindlistRecursive(toInstallList)
        needRebuild <- startsWith(basename(pkgInstall$localFile), "NeedRebuild")
        if (any(needRebuild)) {
          pkgInstall <- needRebuildAndInstall(needRebuild = needRebuild, pkgInstall = pkgInstall,
                                              libPaths = libPaths,
                                              install.packagesArgs = install.packagesArgs,
                                              repos = repos, purge = purge, startTime = startTime, type = type,
                                              pkgInstallTmp = pkgInstallTmp,
                                              tmpdir = tmpdir, verbose = verbose)
        } else {
          pkgInstall <- pkgInstallTmp
        }

        addOK <- if (!is.null(pkgInstall[["installResult"]])) {
          which(is.na(pkgInstall[["installResult"]]))
        } else {
          seq_len(NROW(pkgInstall)) # was NULL, but that is "all rows" in data.table
        }

        set(pkgInstall, addOK,
            c("installed", "Version", "LibPath"),
            list(TRUE, pkgInstall[["VersionOnRepos"]][addOK], libPaths[1])
        )
        pkgInstall <- appendInstallResult(pkgInstall, addOK, installResult = "OK")
        pkgInstallList[[.txtInstall]] <- pkgInstall
      }
      if (!is.null(pkgInstallList[[.txtNoneAvailable]])) {
        pkgInstallList[[.txtNoneAvailable]] <-
          appendInstallResult(pkgInstallList[[.txtNoneAvailable]], seq_len(NROW(pkgInstallList[[.txtNoneAvailable]])),
                              pkgInstallList[[.txtNoneAvailable]]$needInstall)
        set(pkgInstallList[[.txtNoneAvailable]], NULL, "installed", FALSE)
      }

      # pkgInstallList can have install and .txtNoneAvailable
      pkgDTList <- append(pkgDTList[.txtDontInstall], pkgInstallList)
    }
  }
  pkgDT <- rbindlistRecursive(pkgDTList)
}




secondsInADay <- 3600 * 24

urlForArchivedPkgs <- "https://packagemanager.posit.co/cran/"
urlForPositPACKAGES <- file.path(urlForArchivedPkgs, "latest")
# urlForArchivedPkgs <- "https://packagemanager.rstudio.com/cran/"
# urlForArchivedPkgs <- "https://MRAN.revolutionanalytics.com/snapshot"

archivedOn <- function(possiblyArchivedPkg, pkgRelPath, verbose, repos, numGroups, counter,
                       srcPackageURLOnCRAN, repo) {
  Map(
    pk = possiblyArchivedPkg, prp = pkgRelPath, counter = counter, USE.NAMES = TRUE,
    function(pk, prp, counter) {
      uu <- url(paste0("https://cran.r-project.org/package=", pk))
      on.exit(try(close(uu), silent = TRUE))
      rl <- suppressWarnings(try(readLines(uu), silent = TRUE))
      close(uu)
      wasRemoved <- any(grepl("was removed from the CRAN repository", rl))

      archivedOn <- ""
      if (wasRemoved) {
        archivedOn <- grep("Archived on", rl, value = TRUE)
        if (length(archivedOn)) {
          archivedOn <- as.POSIXct(gsub("Archived on (.+) (.)+", "\\1", archivedOn))
          PackageUrl <- prp
        } else {
          # some CRAN repos e.g., RStudioPackage Manager is not a full CRAN mirror; try all repos
          if (all(isBinaryCRANRepo(repos))) {
            repos <- c(repos, CRAN = srcPackageURLOnCRAN)
          }
          for (repo in repos) {
            pp <- if (grepl(urlForArchivedPkgs, repo)) prp else pk
            yy <- url(getArchiveURL(repo, pp)) # posit.co won't return something at repo, pk --> needs prp


            on.exit(try(close(yy), silent = TRUE))
            rl2 <- suppressWarnings(try(readLines(yy), silent = TRUE))
            close(yy)
            suppressWarnings(lineWDateAndPkgFilename <- tail(grep(paste0(pk, ".*tar.gz"), rl2, value = TRUE), 1))
            if (!is(rl2, "try-error")) {
              if (length(lineWDateAndPkgFilename) == 0) {
                next
              }
              if (length(repos) > 1 && !identical(repo, repos[1])) {
                messageVerbose("Found archive at: ", repo, verbose = verbose, verboseLevel = 2)
              }
              break
            }
            messageVerbose("Could not get ", pk, " at ", repo, verbose = verbose, verboseLevel = 2)
            if (length(repos) > 1) {
              messageVerbose("; trying next CRAN repo", verbose = verbose, verboseLevel = 2)
            }
          }


          pkgFilename <- gsub(paste0(".+(", pk, "_.+tar.gz).+.+"), "\\1", lineWDateAndPkgFilename)
          PackageUrl <- file.path(pk, pkgFilename)
          archivedOn <- gsub(".+([[:digit:]]{4,4}-[[:digit:]]{2,2}-[[:digit:]]{2,2}).+", "\\1", lineWDateAndPkgFilename)
          archivedOn <- as.POSIXct(archivedOn) + 5 * 3600 * 24
        }
      } else {
        publishedDate <- grep("Published", rl)
        if (length(publishedDate)) {
          theDateGrep <- "[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}"
          theDate <- grep(theDateGrep, rl[seq(publishedDate, publishedDate + 2)], value = TRUE)
          publishedDate <- gsub(paste0(".+(", theDateGrep, ").+"), "\\1", theDate)
        }

        archivedOn <- publishedDate
        PackageUrl <- ""
      }
      archivedOn <- as.character(as.POSIXct(archivedOn) - 3600 * 24)
      list(PackageUrl = PackageUrl, archivedOn = archivedOn)
    }
  )
}

whichToInstall <- function(pkgDT, install, verbose, libPaths = .libPaths()) {
  set(pkgDT, NULL, "isPkgInstalled", !is.na(pkgDT[["Version"]]))
  set(pkgDT, NULL, "installedVersionOK", !is.na(pkgDT[["Version"]])) # default: if it is installed,  say "OK"
  if (!is.null(pkgDT[[hasHEADtxt]])) {
    whHasHead <- which(pkgDT[[hasHEADtxt]] %in% TRUE)
    set(pkgDT, whHasHead, c("installedVersionOK", "isPkgInstalled"), list(FALSE, FALSE))
  }
  set(
    pkgDT, NULL, "hasVersionsToCompare",
    (nchar(pkgDT[["inequality"]]) > 0) %in% TRUE & !is.na(pkgDT[["Version"]])
  )
  if (any(pkgDT$hasVersionsToCompare %in% TRUE)) {
    pkgDT[hasVersionsToCompare %in% TRUE, installedVersionOK :=
            compareVersion2(Version, versionSpec, inequality)#,
          #by = seq(sum(hasVersionsToCompare))
    ]
  }

  pkgDT <- checkHEAD(pkgDT)
  if (any(pkgDT[[hasHEADtxt]])) {
    ## A `(HEAD)` ref used to force installedVersionOK = FALSE on every row
    ## carrying it -- "always install", whatever is on disk.
    ##
    ## For a GitHub ref that is right: a branch HEAD moves without the
    ## DESCRIPTION version changing, so only a SHA comparison can settle it.
    ## For a CRAN-form ref, "HEAD" just means "the newest the repositories
    ## have", which we can settle here from metadata already in hand. Nothing
    ## downstream did it: the HEAD -> dontInstall comparison lives in
    ## doDownloads(), reachable only from doInstalls(), which is the `else`
    ## branch of `if (getOption("Require.usePak"))`. Under the default
    ## usePak = TRUE it never ran.
    ##
    ## That mattered because updatePackages() tags every installed CRAN
    ## package `pkg (HEAD)`, so Install() was being asked to reinstall the
    ## whole library -- 170 same-version rebuilds where update.packages()
    ## correctly found 3. Install() not reinstalling what you already have is
    ## the point of the package, so the fix belongs here, not in the caller.
    whHEAD <- which(pkgDT[[hasHEADtxt]] %in% TRUE)
    isGHrow <- if (is.null(pkgDT[["repoLocation"]])) rep(TRUE, length(whHEAD))
               else pkgDT[["repoLocation"]][whHEAD] %in% .txtGitHub
    ok <- rep(FALSE, length(whHEAD))

    ## CRAN-alike half: newest the repositories offer, by version.
    vor <- pkgDT[["VersionOnRepos"]]
    if (!is.null(vor)) {
      instV <- pkgDT[["Version"]][whHEAD]
      availV <- vor[whHEAD]
      cmp <- which(!isGHrow & !is.na(instV) & !is.na(availV))
      for (k in cmp) ok[k] <- utils::compareVersion(instV[k], availV[k]) >= 0
    }

    ## GitHub half: newest the branch points at, by SHA. Reuses
    ## alreadyExistingDESCFile(), which is the existing implementation of this
    ## question -- it reads GithubSHA1 out of the installed DESCRIPTION and
    ## compares it with getSHAfromGitHubMemoise(). doDownloads() calls the same
    ## machinery, but it sits on the legacy non-pak path, so under the default
    ## Require.usePak = TRUE nothing compared SHAs and every GitHub HEAD ref
    ## reinstalled unconditionally. Reading the SHA from disk means this does
    ## not depend on pkgDT carrying a local-SHA column.
    haveGHcols <- all(c("Account", "Repo", "Branch") %in% colnames(pkgDT))
    if (any(isGHrow) && haveGHcols) {
      acct <- pkgDT[["Account"]][whHEAD]
      repo <- pkgDT[["Repo"]][whHEAD]
      br <- pkgDT[["Branch"]][whHEAD]
      gh <- which(isGHrow & !is.na(acct) & !is.na(repo) & !is.na(br))
      for (k in gh) {
        ## Anything unresolvable -- no local DESCRIPTION, unreachable GitHub --
        ## leaves ok = FALSE, i.e. the old "install" answer. A network failure
        ## must never read as "you are up to date".
        res <- tryCatch(
          alreadyExistingDESCFile(libPaths = libPaths, Repo = repo[k],
                                  Account = acct[k], Branch = br[k],
                                  installResult = NA_character_,
                                  verbose = verbose),
          error = function(e) NULL)
        if (!is.null(res))
          ok[k] <- identical(res[[3]], .txtShaUnchangedNoInstall)
      }
    }
    set(pkgDT, whHEAD, "installedVersionOK", ok)
  }

  set(pkgDT, NULL, "needInstall", c(.txtDontInstall, .txtInstall)[pkgDT$installedVersionOK %in% FALSE + 1])
  whDontInstall <- pkgDT[["needInstall"]] %in% .txtDontInstall
  if (any(whDontInstall & pkgDT$installedVersionOK %in% FALSE)) {
    messageVerbose(messageCantInstallNoVersion(pkgDT[["packageFullName"]][whDontInstall]),
                   verbose = verbose, verboseLevel = 1)
  }
  if (identical(install, "force")) {
    askedByUser <- !is.na(pkgDT[["loadOrder"]])
    set(pkgDT, which(askedByUser), "needInstall", .txtInstall)
  }

  pkgDT
}


doLoads <- function(require, pkgDT, libPaths, verbose = getOption("Require.verbose")) {
  needRequire <- require
  if (is.character(require)) {
    pkgDT[Package %in% require, require := TRUE]
  } else {
    wh <- if (is.null(pkgDT[["loadOrder"]])) seq_len(NROW(pkgDT)) else which(!is.na(pkgDT[["loadOrder"]]))
    pkgDT[wh, require := isTRUE(require)]
  }

  # override if version was not OK
  if (any(pkgDT$require %in% TRUE)) {
    missingCols <- setdiff(c("installedVersionOK", "availableVersionOK", "installResult", "installed"), colnames(pkgDT))
    if (length(missingCols)) set(pkgDT, NULL, missingCols, NA)
    pkgDT[require %in% TRUE, require := (installedVersionOK %in% TRUE | installResult %in% "OK")]

    ## Fall-back: installation failed but an older version is present.
    ## Load whatever is installed rather than leaving the package completely unattached.
    ## Refusing to load is worse than loading a slightly-old version: the user has already
    ## been warned about the failure; silently skipping the load produces confusing errors
    ## (e.g. "object 'sppEquivalencies_CA' not found") with no hint about the real cause.
    fallback <- pkgDT$require %in% FALSE &
                pkgDT$installResult %in% .txtCouldNotBeInstalled &
                pkgDT$installed %in% TRUE
    if (any(fallback)) {
      fbPkgs  <- pkgDT$Package[fallback]
      fbVers  <- pkgDT$Version[fallback]
      fbSpecs <- pkgDT$packageFullName[fallback]
      warning(
        "Installation failed; loading installed version as fallback:\n",
        paste0("  ", fbPkgs, " (installed: ", fbVers,
               ", required: ", fbSpecs, ")", collapse = "\n"),
        call. = FALSE, immediate. = TRUE
      )
      set(pkgDT, which(fallback), "require", TRUE)
    }
  }


  out <- list()
  if (any(pkgDT$require %in% TRUE)) {
    setorderv(pkgDT, "loadOrder", na.last = TRUE)
    loadedSufficientByPkg <- if ("loadedSufficient" %in% names(pkgDT)) {
      tmp <- pkgDT[require %in% TRUE,
                   list(loadedSufficient = isTRUE(any(loadedSufficient %in% TRUE))),
                   by = "Package"]
      setNames(tmp$loadedSufficient, tmp$Package)
    } else {
      character(0)
    }
    # rstudio intercepts `require` and doesn't work internally
    out[[1]] <- mapply(x = unique(pkgDT[["Package"]][pkgDT$require %in% TRUE]), function(x) {
      warn_msgs <- character(0L)
      # When the package is already loaded with a sufficient version
      # (flagged by useLoadedIfSufficient), call require() WITHOUT lib.loc
      # so R simply attaches the live namespace.  Passing lib.loc would make
      # require() try to resolve the package from libPaths, which can hit
      # the "cannot be unloaded because <X> is imported by <Y>" error path
      # when libPaths has a different version.
      isLoadedSuff <- isTRUE(loadedSufficientByPkg[x])
      res <- withCallingHandlers(
        if (isLoadedSuff)
          base::require(x, character.only = TRUE, quietly = verbose <= 0)
        else
          base::require(x, lib.loc = libPaths, character.only = TRUE, quietly = verbose <= 0),
        warning = function(w) {
          warn_msgs <<- c(warn_msgs, conditionMessage(w))
          invokeRestart("muffleWarning")
        }
      )
      # Recover the common "cannot be unloaded because <package> is imported
      # by <other-pkgs>" failure: R prints that text directly (not as a
      # condition), then require() returns FALSE -- but the namespace IS still
      # loaded (unload failed, so it stayed). Detect via loadedNamespaces()
      # and attach the live namespace directly via attachNamespace(); using
      # require() again re-triggers R's version check + unload attempt, which
      # fails the same way. attachNamespace() bypasses that and just puts the
      # in-memory namespace on the search path.
      if (!isTRUE(res) && x %in% loadedNamespaces()) {
        if (!paste0("package:", x) %in% search()) {
          tryCatch(attachNamespace(x), error = function(e) {})
        }
        res <- paste0("package:", x) %in% search()
        if (isTRUE(res)) warn_msgs <- character(0L)
      }
      if (!isTRUE(res)) {
        ## Always visible regardless of verbose: a silently-unloaded package causes
        ## confusing downstream errors (e.g. "object 'sppEquivalencies_CA' not found").
        hint <- if (length(warn_msgs)) paste0(" (", paste(warn_msgs, collapse = "; "), ")") else ""
        warning("Require: require(\"", x, "\") returned FALSE -- package will not be attached", hint,
                "\n  Searched in: ", paste(libPaths, collapse = ", "),
                call. = FALSE, immediate. = TRUE)
      }
      res
    }, USE.NAMES = TRUE)
  }

  if (any(pkgDT$require %in% FALSE)) {
    out[[2]] <- mapply(x = pkgDT[["Package"]][pkgDT$require %in% FALSE], function(x) FALSE, USE.NAMES = TRUE)
  }
  out <- do.call(c, out)
  out[na.omit(pkgDT[["Package"]][!is.na(pkgDT[["loadOrder"]])])] # put in order, based on loadOrder

  out
}

recordLoadOrder <- function(packages, pkgDT) {
  dups <- duplicated(packages)
  if (any(dups)) {
    packages <- packages[!dups]
  }
  packagesWOVersion <- trimVersionNumber(packages)
  packagesWObase <- setdiff(packagesWOVersion, .basePkgs)
  pfn <- trimVersionNumber(pkgDT[["packageFullName"]])
  # Primary match: full packageFullName after stripping version specs.
  # Fallback match by plain Package name: needed when the user supplied a GitHub
  # ref (e.g. "owner/Pkg@branch", no version spec) that trimRedundantVersionAndNoVersion
  # replaced with a CRAN version-spec ref (e.g. "Pkg (>= X)") because a transitive
  # dep required a minimum version.  In that case pfn = "Pkg" but packagesWObase
  # = "owner/Pkg@branch" -- the packageFullName match fails even though it is the
  # same package.  Matching by Package name catches this.
  packagesWObaseNames <- extractPkgName(packagesWObase)
  wh <- pfn %in% packagesWObase | pkgDT[["Package"]] %in% packagesWObaseNames
  out <- try(pkgDT[wh, loadOrder := seq(sum(wh))])
  pkgDT[, loadOrder := na.omit(unique(loadOrder))[1], by = "Package"]

  if (is(out, "try-error")) {
    browserDeveloper("Error 1253; please contact developer")
  }
  pkgDT
}

removeDups <- function(pkgDT) {
  dups <- duplicated(pkgDT[["packageFullName"]])
  pkgDT[!dups]
}

dealWithStandAlone <- function(pkgDT, libPaths, standAlone) {
  if (isTRUE(standAlone)) {
    # Remove any packages that are not in libPaths[1], i.e., the main R library
    notInLibPaths1 <- (!pkgDT[["Package"]] %in% .basePkgs) &
      (!normPath(pkgDT$LibPath) %in% normPath(libPaths)) & !is.na(pkgDT$LibPath)
    if (any(notInLibPaths1)) {
      notInLP1 <- "notInLibPaths1"
      set(pkgDT, which(notInLibPaths1), notInLP1, TRUE)

      # This will set the Version in that libPaths to NA, meaning it will be considered uninstalled,
      #   so the machinery will install it in libPaths[1]
      pkgDT[notInLibPaths1, `:=`(
        installed = FALSE,
        LibPath = NA_character_,
        Version = NA_character_
      )]
      pkgDT <- pkgDT[!(duplicated(pkgDT, by = "packageFullName") &
                         notInLibPaths1 %in% TRUE)] # this will remove any that are identical packageFullName, which will be because they are in different libPaths
      set(pkgDT, NULL, notInLP1, NULL)
    }
    setorderv(pkgDT, c("Package", "LibPath"), na.last = TRUE)
  }
  pkgDT
}


doDownloads <- function(pkgInstall, repos, purge, verbose, install.packagesArgs,
                        libPaths, tmpdir, type = getOption("pkgType")) {
  pkgInstall[, installSafeGroups := 1L]

  # this is a placeholder; set noLocal by default
  set(pkgInstall, NULL, "haveLocal", .txtNoLocal)

  # This sequence checks for the many redundancies, i.e., >= 1.0.0 is redundant with >= 0.9.0; so keep just first
  pkgInstall <- trimRedundancies(pkgInstall, repos, purge, libPaths,
                                 verbose = verbose,
                                 type = type
  )
  # When GITHUB_PAT rate limit is hit, the error message in Require is useless;
  #   use `remotes`
  pkgInstall <- withCallingHandlers(
    checkAvailableVersions(pkgInstall, repos, purge, libPaths,
                           verbose = verbose, type = type),
    error = function(e) {
      e$message <- gsub(paste0("(https://).*@"), "\\1", e$message)
      stop(e, "\nDoes the url exist? ",
           "Are you not using a GITHUB_PAT? Do you have an old GITHUB_PAT?\n",
           "For better error handling, install.packages('remotes')")
    })
  naRepository <- is.na(pkgInstall$Repository)
  if (any(naRepository)) {
    if (!is.null(pkgInstall$Additional_repositories))
      pkgInstall[naRepository, Repository := Additional_repositories]
  }

  # Will set `haveLocal = .txtLocal` and `installFrom = .txtLocal`
  pkgInstall <- identifyLocalFiles(pkgInstall, repos, purge, libPaths = libPaths, verbose = verbose)
  pkgInstallList <- split(pkgInstall, by = "haveLocal")
  if (!is.null(pkgInstallList$Local)) {
    # could be duplicated if repos had multiple versions of the package; only need 1 from local installed
    pkgInstallList$Local <- pkgInstallList$Local[, .SD[availableVersionOK %in% TRUE][1], by = "Package"]
  }

  pkgNeedInternet <- pkgInstallList[[.txtNoLocal]] # pointer
  numToDownload <- NROW(pkgInstallList[[.txtNoLocal]])

  if (NROW(pkgNeedInternet)) {
    if (getOption("Require.offlineMode", FALSE)) {
      wh <- which(!nzchar(pkgNeedInternet[["localFile"]]))
      pkgNeedInternet[wh, needInstall := .txtDontInstall]
      pkgNeedInternet[wh, installResult := .txtNoInternetNoLocalCantInstall]
    } else {

      pkgNeedInternet <- split(pkgNeedInternet, by = "repoLocation")

      # CRAN
      pkgNeedInternet <- downloadCRAN(pkgNeedInternet, repos, purge, install.packagesArgs,
                                      verbose, numToDownload,
                                      type = type, tmpdir = tmpdir
      )

      # Archive
      pkgNeedInternet <- downloadArchive(
        pkgNeedInternet, repos, purge = purge, install.packagesArgs,
        numToDownload, tmpdir = tmpdir, verbose = verbose - 1
      )
      if (!is.null(pkgNeedInternet$Archive))
        pkgNeedInternet$Archive[nchar(localFile) == 0 | is.na(localFile), needInstall := .txtNoneAvailable]

      # GitHub
      pkgNeedInternet <- downloadGitHub(
        pkgNeedInternet, libPaths, tmpdir = tmpdir, verbose, install.packagesArgs,
        numToDownload
      )
      if (!is.null(pkgNeedInternet$GitHub)) {
        if (!is.null(pkgNeedInternet$GitHub[["SHAonLocal"]])) {
          pkgNeedInternet$GitHub[SHAonGH == SHAonLocal, needInstall := .txtShaUnchangedNoInstall]
        }
        pkgNeedInternet$GitHub[(nchar(localFile) == 0 | is.na(localFile)) & needInstall != .txtShaUnchangedNoInstall,
                               needInstall := .txtNoneAvailable]
      }

    }
    pkgInstallList[[.txtNoLocal]] <- pkgNeedInternet # pointer

  }
  pkgInstall <- rbindlistRecursive(pkgInstallList)

  # fix the dependencies for the ones that were not availableVersionOKthisOne
  pkgInstallAOK <- split(pkgInstall, by = "availableVersionOKthisOne")
  pp <- pkgInstallAOK[["FALSE"]]#[availableVersionOKthisOne %in% FALSE]
  if (NROW(pp)) {
    nih <- "needInstallHere"
    set(pp, NULL, nih, pp[["needInstall"]] != .txtNoneAvailable)
    pp1 <- split(pp, by = nih)
    if (NROW(pp1[["TRUE"]])) {
      rr <- by(pp1[["TRUE"]], seq_len(NROW(pp1[["TRUE"]])),
               function(pu, lf) {
                 with(pu, {
                   DF <- .DESCFileFull(PackageUrl = pu$localFile,
                                       Package = pu$Package, verbose = verbose,
                                       tmpdir = tmpdir)
                 }
                 )}) |> setNames(pp1[["TRUE"]]$packageFullName)
      colsHere <- setdiff(colsOfDeps, "Remotes")
      a <- DESCRIPTIONFileDepsV(rr, which = colsHere, keepSeparate = TRUE)
      b <- invertList(a)
      d <- as.data.table(b)
      for (ch in colsHere)
        set(d, NULL, ch, sapply(d[[ch]], function(x) paste0(x, collapse = ",")))
      set(d, NULL, c("Package", "packageFullName"),
          list(pp1[["TRUE"]]$Package, pp1[["TRUE"]]$package))
      pp1[["TRUE"]] <- pp1[["TRUE"]][, -..colsHere][d, on = c("Package", "packageFullName")]
      pp <- rbindlist(pp1, fill = TRUE)
    }
    set(pp, NULL, nih, NULL)
    pkgInstall <- rbindlist(
      append(pkgInstallAOK[setdiff(names(pkgInstallAOK), "FALSE")], list(pp)),
      fill = TRUE)
  }

  # This will potentially do Archive (HEAD)
  pkgInstall <- removeHEADpkgsIfNoUpdateNeeded(pkgInstall, verbose = verbose)

  pkgInstall
}

getVersionOnRepos <- function(pkgInstall, repos, purge, libPaths, type = getOption("pkgType")) {

  for (i in 1:2) {
    ap <- available.packagesCached(repos = repos, purge = purge, type = type)[, ..apCachedCols]
    hasRepos <- unlist(lapply(repos, function(xx) {
      # need try: invalid regular expression 'https://raw.githubusercontent.com/r-hub/repos/main/ubuntu-22.04/4.5/libc++', reason 'Invalid use of repetition operators'
      tryCatch(any(grepl(pattern = xx, x = unique(ap$Repository))), silent = TRUE, error = function(e) FALSE)
    }))
    if (all(hasRepos)) {
      break
    }
    purgeAvailablePackages(repos, purge = TRUE)
  }

  setnames(ap, old = "Version", new = "VersionOnReposCurrent")

  # if both have a column, this creates i.XXX columns
  rmFromPkgInstall <- intersect(colnames(ap), colnames(pkgInstall)) |> setdiff("Package")
  if (length(rmFromPkgInstall))
    set(pkgInstall, NULL, rmFromPkgInstall, NULL)
  pkgInstall <- ap[pkgInstall, on = "Package"]
  whHasVoR <- which(!is.na(pkgInstall[["VersionOnReposCurrent"]]))
  if (is.null(pkgInstall[["VersionOnRepos"]]))
    set(pkgInstall, NULL, "VersionOnRepos", pkgInstall[["VersionOnReposCurrent"]])
  if (length(whHasVoR))
    set(pkgInstall, whHasVoR, "VersionOnRepos", pkgInstall[["VersionOnReposCurrent"]][whHasVoR])
  set(pkgInstall, NULL, "VersionOnReposCurrent", NULL)

  if (any(pkgInstall$repoLocation %in% .txtGitHub)) {
    if (!is.null(pkgInstall[["i.VersionOnRepos"]]))
      pkgInstall[repoLocation %in% .txtGitHub, VersionOnRepos := i.VersionOnRepos]
  }

  pkgInstallList <- split(pkgInstall, by = "repoLocation")
  if (!is.null(pkgInstallList[[.txtGitHub]])) {
    # If there are 2 repos for a package, must clear out one of them for GitHub packages
    #   -- the repos are essentially moot for GitHub packages, but will mess downstream with version numbers
    dupReposForGHPkgs <- pkgInstallList[[.txtGitHub]][, .N, by = "Package"][N > 1]
    if (NROW(dupReposForGHPkgs)) {
      pkgInstallList[[.txtGitHub]][Package %in% dupReposForGHPkgs[["Package"]] &
                                     get(hasHEADtxt) %in% TRUE,
                                   VersionOK := FALSE]

      pkgInstallList[[.txtGitHub]][Package %in% dupReposForGHPkgs[["Package"]] &
                                     get(hasHEADtxt) %in% FALSE,
                                   VersionOK := compareVersion2(version = VersionOnRepos,
                                                                versionSpec = versionSpec,
                                                                inequality = inequality),
                                   by = "Package"]
      setorderv(pkgInstallList[[.txtGitHub]], "VersionOK", order = -1L, na.last = TRUE)
      pkgInstallList[[.txtGitHub]] <- pkgInstallList[[.txtGitHub]][, .SD[1], by = "Package"]
      set(pkgInstallList[[.txtGitHub]], NULL, "VersionOK", NULL)
    }
    # packages that are both on GitHub and CRAN will get a VersionOnRepos; if the request is to load from GH, then change to NA
    pkgInstallList[[.txtGitHub]][repoLocation %in% .txtGitHub, VersionOnRepos := NA]
    pkgInstallList[[.txtGitHub]] <- getGitHubVersionOnRepos(pkgInstallList[[.txtGitHub]])
  }
  pkgInstall <- rbindlistRecursive(pkgInstallList)
  pkgInstall <- getVersionOnReposLocal(pkgInstall)
  pkgInstall
}


# CRAN
downloadCRAN <- function(pkgNoLocal, repos, purge, install.packagesArgs, verbose, numToDownload,
                         tmpdir, type = getOption("pkgType")) {
  pkgCRAN <- pkgNoLocal[["CRAN"]]
  if (NROW(pkgCRAN)) { # CRAN, Archive, RSPM
    # messageVerbose(messageDownload(pkgCRAN, NROW(pkgCRAN), "CRAN"), verbose = verbose, verboseLevel = 2)
    if (!all(apCachedCols %in% colnames(pkgCRAN))) {
      ap <- available.packagesCached(repos = repos, purge = purge, type = type)[, ..apCachedCols]
      setnames(ap, old = "Version", new = "VersionOnRepos")
      pkgNoLocal[["CRAN"]] <- ap[pkgCRAN, on = "Package"]
      pkgCRAN <- pkgNoLocal[["CRAN"]] # pointer
    }

    # Not on CRAN; so likely Archive
    notOK <- !pkgCRAN$availableVersionOK %in% TRUE # FALSE means it is on CRAN, but not that version; NA means it is not on CRAN currently
    if (any(notOK)) {
      pkgNot <- unique(pkgCRAN[["packageFullName"]][notOK])
      pkgCRAN[notOK, `:=`(repoLocation = .txtArchive, installFrom = .txtArchive)]
      pkgNoLocal[["CRAN"]] <- pkgCRAN
      pkgNoLocal <- rbindlistRecursive(pkgNoLocal)
      pkgNoLocal <- split(pkgNoLocal, by = "repoLocation")
      pkgCRAN <- pkgNoLocal[["CRAN"]]
    }
    if (NROW(pkgCRAN)) {
      if (isTRUE(any(duplicated(pkgCRAN$Package)))) {
        pkgCRAN <- updateReposForSrcPkgs(pkgCRAN, verbose = verbose)
      }



      ap <- pkgCRAN[pkgCRAN$availableVersionOK %in% TRUE]
      if (getOption("Require.installPackagesSys") == 2 && NROW(ap)) {
        # ap <- pkgCRAN[pkgCRAN$availableVersionOK %in% TRUE]
        args <- list(repos = repos, type = type)
        file <- paste0(ap[["Package"]], "_", ap$VersionOnRepos)
        if (isWindows() && (identical(type, "both") || grepl("bin", type))) {
          # args$type <- "binary"
          packageUrl <- file
          fileext <- c(".tar.gz", ".zip")[(ap[["binOrSrc"]] %in% "bin") + 1]
        } else {
          if (any(ap$repoLocation %in% "CRAN"))
            packageUrl <- file
          else
            packageUrl <- file.path(ap[["Package"]], file)
          if (isMacOS())
            fileext <- c(".tar.gz", macBinaryFileExt)[(ap[["binOrSrc"]] %in% "bin") + 1]
            # fileext <- macBinaryFileExt
          else
            fileext <- ".tar.gz"
        }
        packageUrl <- paste0(packageUrl, fileext)
        args$url <- file.path(ap$Repository, packageUrl)
        args$destfile <- file.path(tmpdir, basename(args$url))

        on.exit(copyBuiltToCache(pkgCRAN, tmpdirs = tmpdir, copyOnly = TRUE), add = TRUE)

        st <- system.time(
          dt <- sysInstallAndDownload(args = args, splitOn = c("url", "destfile"),
                                      doLine = "outfiles <- try(do.call(download.file, args))",
                                      tmpdir = tmpdir,
                                      verbose = verbose)
        )
        if (!getOption("Require.offlineMode") %in% TRUE) {
          messageVerbose("  CRAN ", downloadedInSeconds(st[[3]]), verbose = verbose)
          pkgCRAN[dt, localFile := i.localFile, on = "Package"]
          pkgCRAN[availableVersionOK %in% TRUE, installFrom := .txtLocal]
          pkgCRAN[availableVersionOK %in% TRUE, newLocalFile := TRUE]
        } else {
          pkgCRAN[availableVersionOK %in% TRUE, newLocalFile := FALSE]
        }
      } else {
        pkgCRAN[availableVersionOK %in% TRUE, installFrom := "CRAN"]
        pkgCRAN[, localFile := useRepository]
      }

    }
  }
  pkgNoLocal # pkgCRAN is already in this because it was a pointer
}

#' @importFrom utils contrib.url
downloadArchive <- function(pkgNonLocal, repos, purge = FALSE, install.packagesArgs,
                            numToDownload, tmpdir, verbose) {
  # fillDefaults(pkgDep)
  pkgArchive <- pkgNonLocal[[.txtArchive]]
  if (NROW(pkgArchive)) {
    ava <- dlArchiveVersionsAvailable(unique(pkgArchive[["Package"]][pkgArchive$repoLocation %in% .txtArchive]),
                                      repos = repos, verbose = verbose
    )
    if (!isTRUE(getOption("Require.offlineMode"))) {
      pkgArchive <- getArchiveDetails(pkgArchive, ava, verbose, repos)
      hasPackageUrl <- !is.na(basename(pkgArchive$PackageUrl))

      if (any(hasPackageUrl)) {
        pkgArchiveHasPU <- split(pkgArchive, f = hasPackageUrl)

        tf <- file.path(cachePkgDirForRepo(repos[1]), basename(pkgArchiveHasPU$`TRUE`$PackageUrl))
        fe <- file.exists(tf)
        if (any(fe)) {
          messageVerbose(
            blue("  -- ", unique(paste(pkgArchiveHasPU$`TRUE`[["packageFullName"]][fe], collapse = comma)), " ",
                 isAre(l = pkgArchiveHasPU$`TRUE`[["packageFullName"]][fe]),
                 " not on CRAN; have local cached copy"),
            verbose = verbose, verboseLevel = 1
          )
          whfe <- which(fe)
          pkgArchiveHasPU$`TRUE`[whfe, haveLocal := .txtLocal]
          pkgArchiveHasPU$`TRUE`[whfe, localFile := tf[whfe]]
          pkgArchiveHasPU$`TRUE`[whfe, installFrom := haveLocal]

        }
        whNotfe <- which(fe %in% FALSE)
        if (any(!fe)) {
          messageVerbose(
            blue("  -- ", unique(paste(pkgArchiveHasPU$`TRUE`[["packageFullName"]][whNotfe], collapse = comma)), " ",
                 isAre(l = pkgArchiveHasPU$`TRUE`[["packageFullName"]][whNotfe]),
                 " not on CRAN; trying Archives"),
            verbose = verbose, verboseLevel = 1
          )
          # }

          if (any(pkgArchiveHasPU$`TRUE`[, .N, by = "Package"]$N > 1)) {
            # keep user supplied repos order, in case there are multiple repos that have the package
            set(pkgArchiveHasPU$`TRUE`, NULL, "repoOrder", match(pkgArchiveHasPU$`TRUE`$repo, repos))
            # The na.last are the repositories that didn't have the package in archives; get rid of it
            setorderv(pkgArchiveHasPU$`TRUE`, c("Package", "repoOrder"), order = c(1L, 1L), na.last = TRUE)
            pkgArchiveHasPU$`TRUE` <- pkgArchiveHasPU$`TRUE`[, .SD[1], by = "Package"]
            set(pkgArchiveHasPU$`TRUE`, NULL, "repoOrder", NULL)
          }
          # Check RSPM
          # pkgArchiveHasPU$`TRUE` <- downloadRSPM(pkgArchiveHasPU$`TRUE`, install.packagesArgs, verbose)

          if (any(pkgArchiveHasPU$`TRUE`$repoLocation %in% .txtArchive &
                  pkgArchiveHasPU$`TRUE`$availableVersionOK %in% TRUE)) {
            pkgArchiveHasPU$`TRUE` <- split(pkgArchiveHasPU$`TRUE`, pkgArchiveHasPU$`TRUE`[["repoLocation"]])
            pkgArchOnly <- pkgArchiveHasPU$`TRUE`[[.txtArchive]]

            # The non sys doesn't work. It needs to download the file; but doesn't
            # if (getOption("Require.installPackagesSys") >= 1) {
              on.exit(copyBuiltToCache(pkgArchOnly, tmpdir, copyOnly = TRUE))

              pkgArchOnly <- archiveDownloadSys(pkgArchOnly, whNotfe, tmpdir = tmpdir, verbose)
              pkgArchiveHasPU$`TRUE`[[.txtArchive]] <- pkgArchOnly

            # } else {
            #   # Package name is already in the PackageUrl
            #   pkg <- rep("", NROW(pkgArchOnly))
            #   havePkgAlreadyInPkgUrl <- dirname(pkgArchOnly$PackageUrl) != pkgArchOnly$Package
            #   if (any(havePkgAlreadyInPkgUrl)) {
            #     pkg[havePkgAlreadyInPkgUrl] <- pkgArchOnly$Package
            #   }
            #   pkgArchOnly[whNotfe, Repository := getArchiveURL(repo, pkg = "", type = "source")]
            #
            #   # pkgArchOnly[whNotfe, Repository := file.path(contrib.url(repo, type = "source"), .txtArchive, Package)]
            #   pkgArchOnly[whNotfe, localFile := useRepository]
            #
            # }

          }
        }
        pkgArchiveHasPU$`TRUE` <- rbindlistRecursive(pkgArchiveHasPU$`TRUE`)
        pkgArchive <- rbindlist(pkgArchiveHasPU, fill = TRUE, use.names = TRUE)
      }
    }
  }

  pkgNonLocal[[.txtArchive]] <- pkgArchive

  pkgNonLocal
}

downloadGitHub <- function(pkgNoLocal, libPaths, verbose, install.packagesArgs, tmpdir, numToDownload) {
  pkgGitHub <- pkgNoLocal[[.txtGitHub]]
  if (NROW(pkgGitHub)) { # GitHub
    messageVerbose(messageDownload(pkgGitHub, NROW(pkgGitHub), .txtGitHub), verbose = verbose, verboseLevel = 1)

    # If there was a local cache check, then this was already done; internally this will be fast/skip check
    pkgGitHub <- getGitHubVersionOnRepos(pkgGitHub)
    pkgGitHub <- availableVersionOK(pkgGitHub)
    avOK <- which(pkgGitHub$availableVersionOK %in% TRUE)

    if (length(avOK)) {
      if (is.null(pkgGitHub[["SHAonGH"]])) {
        colsToUpdate <- c("SHAonGH")
        set(pkgGitHub, NULL, colsToUpdate, list(NA_character_)) # fast to just do all; then next lines may update
        pkgGitHub[avOK, (colsToUpdate) := {
          SHAonGH <- getSHAfromGitHubMemoise(repo = Repo, acct = Account, br = Branch, verbose = verbose)
        }, by = "Package"]
        # saveGitHubSHAsToDisk()
      }

      pkgGHList <- split(pkgGitHub, pkgGitHub$availableVersionOK)
      pkgGHtoDL <- pkgGHList[["TRUE"]]
      if (any(!pkgGHtoDL[["installedVersionOK"]])) {
        # download and build in a separate dir; gets difficult to separate this addition from existing files otherwise
        td <- tempdir3()
        prevDir <- setwd(td)
        on.exit({
          cleanUpNewBuilds(pkgGHtoDL, prevDir) # no need to assign this because it is on.exit fail
          if (isTRUE(needRmGSF))
            set(pkgGHtoDL, NULL, "GitSubFolder", NULL)
        })
        needRmGSF <- FALSE
        if (!"GitSubFolder" %in% names(pkgGHtoDL)) {
          set(pkgGHtoDL, NULL, "GitSubFolder", NA_character_)
          needRmGSF <- TRUE
        }

        if (getOption("Require.installPackagesSys") == 2) {
          aa <- pkgGHtoDL[!SHAonGH %in% FALSE]
          args <- list(Account = aa$Account, Repo = aa$Repo, Branch = aa$Branch,
                       GitSubFolder = aa$GitSubFolder, verbose = verbose, Package = aa[["Package"]],
                       VersionOnRepos = aa$VersionOnRepos)
          splitOn = c("Account", "Repo", "Branch", "GitSubFolder", "VersionOnRepos", "Package")

          st <- system.time(
            dt <- try(
              sysInstallAndDownload(args, splitOn = splitOn, tmpdir = tmpdir, doLineVectorized = FALSE,
                                        "outfiles <- do.call(Require:::downloadAndBuildToLocalFile, args)",
                                        libPaths = libPaths, verbose = verbose)
          ))
          if (is(dt, "try-error")) stop("Download failed; perhaps flaky internet or file to large?")
          messageVerbose("  GitHub ", downloadedInSeconds(st[[3]]), verbose = verbose)
          pkgGHtoDL[dt, localFile := i.localFile, on = "Package"]
          pkgGHtoDL[!SHAonGH %in% FALSE, installFrom := .txtLocal]
          pkgGHtoDL[!SHAonGH %in% FALSE, newLocalFile := TRUE]
        } else {
          pkgGHtoDL[!SHAonGH %in% FALSE, localFile := {
            downloadAndBuildToLocalFile(Account, Repo, Branch, Package, GitSubFolder, verbose, VersionOnRepos)
          }, by = "Package"]
        }

        empty <- !nzchar(pkgGHtoDL[!SHAonGH %in% FALSE]$localFile)
        if (any(empty))
          pkgGHtoDL[which(!SHAonGH %in% FALSE)[empty %in% TRUE], localFile := "NeedRebuild"]
        # A bit of cleaning; this will get rid of the source files; we have the tar.gz after `build`
        pkgGHtoDL[, installFrom := .txtGitHub]
        pkgGHtoDL <- renameLocalGitPkgDT(pkgGHtoDL)
        pkgGHtoDL <- cleanUpNewBuilds(pkgGHtoDL, prevDir)
        if (isTRUE(needRmGSF))
          set(pkgGHtoDL, NULL, "GitSubFolder", NULL)
        on.exit() # don't run on.exit above if this was successful
      }
      pkgGitHub <- rbindlistRecursive(pkgGHList)
    }
  }
  pkgNoLocal[[.txtGitHub]] <- pkgGitHub
  pkgNoLocal
}

cleanUpNewBuilds <- function(pkgDT, prevDir) {
  files <- dir()
  # unlink(files[!endsWith(files, "tar.gz")]) # There was a random .zip file; couldn't tell why; remove it
  # unlink(files[dir.exists(files)], recursive = TRUE) # remove directories
  moveFileToCacheOrTmp(pkgDT)
  setwd(prevDir)
  pkgDT
}

types <- function(length = 1L) {
  isOldMac <- isMacOS() && compareVersion(as.character(getRversion()), "4.0.0") < 0
  types <- if (isOldMac) {
    c("mac.binary.el-capitan", "source")
  } else if (!isWindows() && !isMacOS()) {
    c("source")
  } else {
    c("binary", "source")
  }
  if (identical(length, 1L)) {
    types <- types[1]
  }
}

doPkgSnapshot <- function(packageVersionFile, purge, libPaths,
                          install_githubArgs, install.packagesArgs, standAlone = TRUE,
                          type = getOption("pkgType"), verbose = getOption("Require.verbose"),
                          returnDetails, ...) {
  if (!isFALSE(packageVersionFile)) {
    if (isTRUE(packageVersionFile)) {
      packageVersionFile <- getOption("Require.packageVersionFile")
    }
    packages <- data.table::fread(packageVersionFile)

    if (packages[1, "Package"] == "R") {
      Rversion <- packages[["Version"]][1] |> versionMajorMinor()
      if (!compareVersion2(versionMajorMinor(), Rversion, inequality = "=="))
        ## warning(), not messageVerbose(): a snapshot pinned under a different
        ## R routinely fails to build -- packages pinned under an older R can
        ## use headers or functions the current one has removed (e.g. Calloc /
        ## Free before STRICT_R_HEADERS, is.R() now defunct) -- and the symptom
        ## is hundreds of lines of compiler output with no statement of the
        ## cause. A message at a verbosity level is suppressed by default in
        ## quiet sessions and swallowed entirely under testthat, so the one
        ## piece of information that explains the failure was the piece most
        ## likely to be lost.
        warning("The package snapshot was made using R ", Rversion,
                ". Current session is running R ", getRversion(),
                ". There may be difficulties installing packages; ",
                "if there are, please restart the session using R ", Rversion,
                call. = FALSE)
      packages <- packages[-1, ]
    }

    ## Optional fast-path: bypass pak's solver entirely for snapshot installs.
    ## Snapshots already pin exact versions, so resolution is wasted work.
    ## Gated on options(Require.snapshotInstaller = "install.packages").
    ##
    ## Cross-platform: Linux uses PPM __linux__/<codename> binaries (set via
    ## detectPPMRepo()); macOS uses PPM /cran/latest with User-Agent
    ## content-negotiation for Mac binaries; Windows falls through to source
    ## from CRAN. macOS sysreqs (homebrew under /opt/homebrew vs /usr/local)
    ## are handled by install.packages's per-package configure scripts —
    ## any genuine compile failure surfaces in the post-install diagnostic
    ## report with the offending package and last log lines, instead of an
    ## opaque hang. Windows isn't routinely tested but the same path runs.
    installer <- getOption("Require.snapshotInstaller", "pak")
    if (identical(installer, "install.packages")) {
      out <- installSnapshotViaInstallPackages(packages, libPaths = libPaths,
                                               verbose = verbose)
      messageVerbose(
        "PLEASE RESTART R using the correct library to start using the installed snapshot",
        verbose = verbose)
      return(invisible(out))
    }

    packages <- dealWithSnapshotViolations(packages,
                                           verbose = verbose, purge = purge,
                                           libPaths = libPaths, type = type, install_githubArgs = install_githubArgs,
                                           install.packagesArgs = install.packagesArgs
    ) # i.e., packages that can't coexist
    packages <- packages[!packages[["Package"]] %in% .basePkgs]
    repos <- getOption("repos")
    if (!is.null(packages$Repository)) {
      reposPoss <- unique(na.omit(packages$Repository))
      reposPoss <- setdiff(reposPoss, "CRAN")
      isRSPM <- reposPoss == "RSPM"
      RSPM <- if (any(isRSPM))
        reposPoss <- c(RSPM = urlForPositPACKAGES, reposPoss[!isRSPM])
      repos <- c(reposPoss, repos)
      dups <- duplicated(repos)
      if (any(dups)) {
        repos <- repos[!dups]
      }
    }

    # Get Require dependencies to omit them: it has to exist locally unless this is first install
    # packages <- removeRequireDeps(packages)

    need <- packages[["packageFullName"]]
    names(need) <- rep("", NROW(need))
    # isGH <- isGitHub(packages[["packageFullName"]])
    funnyNames <- packages[["Package"]] != extractPkgName(packages[["packageFullName"]])
    if (any(funnyNames)) {
      names(need)[funnyNames] <- packages[["Package"]][funnyNames]
    }
    ## A snapshot is a closed, fully pinned set: tell the pak path so it
    ## resolves the whole set once, installs it in dependency order with
    ## dependencies = FALSE, and never lets a lone ref pull its deps at
    ## unpinned versions (see pakPinnedInstall in pak.R).
    assign("pakPinnedInstall", TRUE, envir = pakEnv())
    on.exit(rm(list = intersect(c("pakPinnedInstall", "pakDepGraph"),
                                ls(pakEnv(), all.names = TRUE)),
               envir = pakEnv()), add = TRUE)
    out <- Require(need,
                   verbose = verbose, purge = purge, libPaths = libPaths, repos = repos,
                   install_githubArgs = install_githubArgs,
                   install.packagesArgs = install.packagesArgs,
                   standAlone = standAlone, require = FALSE, install = TRUE,
                   returnDetails = returnDetails, ...
    )

    messageVerbose("PLEASE RESTART R using the correct library to start using the installed snapshot",
                   verbose = verbose)
  } else {
    out <- FALSE
  }
  out
}

dealWithSnapshotViolations <- function(pkgSnapshotObj, install_githubArgs, install.packagesArgs,
                                       verbose = getOption("Require.verbose"),
                                       purge = getOption("Require.purge", FALSE),
                                       libPaths = .libPaths(),
                                       repos = getOption("repos"), type = getOption("pkgType")) {
  dd <- pkgSnapshotObj
  pkgDT <- toPkgDT(packageFullNameFromSnapshot(dd))
  toRm <- c(# "Package",
    "Version", "LibPath", "Depends", "Imports", "LinkingTo",
    "Remotes", "GithubRepo", "GithubUsername", "GithubRef", "GithubSHA1")#,
  # "Repository")
  keepCols1 <- setdiff(colnames(dd), toRm)
  set(pkgDT, NULL, keepCols1, dd[, ..keepCols1])

  pkgDT <- trimRedundancies(pkgDT, repos = repos, purge = purge, libPaths = libPaths,
                            verbose = verbose, type = type)
  return(pkgDT)
}

apCachedCols <- c("Package", "Repository", "Version", "Archs", "Depends", "Imports", "Suggests", "LinkingTo")


localFilename <- function(pkgInstall, localFiles, libPaths, verbose) {
  pkgWhere <- split(pkgInstall, pkgInstall[["repoLocation"]])
  pkgGitHub <- pkgWhere[[.txtGitHub]] # pointer
  if (NROW(pkgWhere[[.txtGitHub]])) {
    pkgGitHub <- getGitHubVersionOnRepos(pkgGitHub)
    pkgGitHub <- availableVersionOK(pkgGitHub)
    avOK <- which(pkgGitHub$availableVersionOK %in% TRUE)
    colsToUpdate <- c("SHAonLocal", "SHAonGH", "installResult")
    set(pkgGitHub, NULL, colsToUpdate[1:2], list(NA_character_, NA_character_)) # fast to just do all; then next lines may update
    if (is.null(pkgGitHub[["installResult"]]))
      set(pkgGitHub, NULL, "installResult", NA_character_)

    if (length(avOK)) {
      pkgGitHub[avOK, (colsToUpdate) := {
        alreadyExistingDESCFile(libPaths = libPaths, Repo, Account, Branch, installResult, verbose)
      }, by = "packageFullName"]
    }
    # saveGitHubSHAsToDisk()
    prevInstallResult <- pkgGitHub$installResult
    rowsToUpdate <- which(pkgGitHub$SHAonLocal == pkgGitHub$SHAonGH & pkgGitHub[["installResult"]] == .txtShaUnchangedNoInstall)

    pkgGitHub[rowsToUpdate, `:=`(needInstall = FALSE, haveLocal = .txtLocal, installedVersionOK = TRUE)]
    pkgGitHub <- appendInstallResult(pkgGitHub, rowsToUpdate, installResult = "OK", sep = "; ")

    pkgWhere[[.txtGitHub]] <- pkgGitHub
    pkgInstall <- rbindlistRecursive(pkgWhere)
  }

  pkgInstall[, localFile := localFileID(
    Package, localFiles, repoLocation, SHAonGH,
    inequality, VersionOnRepos, versionSpec, verbose = verbose
  ), by = seq(NROW(pkgInstall))]

  pkgInstall
}

#' Needs `VersionOnRepos`, `versionSpec` and `inequality` columns
#' @param pkgDT A `pkgDT` object
availableVersionOK <- function(pkgDT) {
  # First set all to availableVersionOK if there is a version available
  set(pkgDT, NULL, "availableVersionOK", !is.na(pkgDT[["VersionOnRepos"]]))

  availableOKcols <- c("availableVersionOK", "availableVersionOKthisOne")
  hasAtLeastOneNonNA <- !is.na(pkgDT[["inequality"]]) & !is.na(pkgDT[["VersionOnRepos"]])
  if (any(hasAtLeastOneNonNA)) {
    pkgDT[, hasAtLeastOneNonNA := any(hasAtLeastOneNonNA), by = "Package"]
    toUpdate <- hasAtLeastOneNonNA %in% TRUE
    whToUpdate <- which(toUpdate)
    if (!is.null(pkgDT[[hasHEADtxt]])) {
      if (any(pkgDT[[hasHEADtxt]])) {
        whToUpdate1 <- which(toUpdate & pkgDT[[hasHEADtxt]])
        whToUpdate <- which(toUpdate & pkgDT[[hasHEADtxt]] %in% FALSE)
        set(pkgDT, whToUpdate1, "availableVersionOK", TRUE)
        set(pkgDT, whToUpdate1, "availableVersionOKthisOne", TRUE)
      }
    }

    if (length(whToUpdate)) {
      out <- try(pkgDT[whToUpdate, (availableOKcols) := {
        avokto(versionSpec, VersionOnRepos, inequality)
      }, by = "Package"])

      if (is(out, "try-error")) {
        browserDeveloper("Error 553; please contact developer")
      }
    }
  } else {
    pkgDT[!is.na(VersionOnRepos), #  | is.na(versionSpec),
          (availableOKcols) := list(TRUE, TRUE)]
  }
  pkgDT
}

#' Compare package versions
#'
#' Alternative to `utils::compareVersion` that is vectorized on `version`,
#' `versionSpec` and/or `inequality`. This will also return an NA element
#' in the returned vector if one of the arguments has NA for that element.
#'
#' @param version One or more package versions. Can be `character` or `numeric_version`.
#' @param versionSpec One or more versions to compare to.
#'   Can be `character` or `numeric_version`.
#' @param inequality The inequality to use, i.e., `>=`.
#' @return a logical vector of the length of the longest of the 3 arguments.
#'
#' @family version specifications
#' @export
compareVersion2 <- function(version, versionSpec, inequality) {
  if (isTRUE(any(is(version, "numeric_version"))))
    version <- as.character(version)
  if (isTRUE(any(is(versionSpec, "numeric_version"))))
    versionSpec <- as.character(versionSpec)
  vsIsCharNA <- versionSpec %in% "NA"
  if (any(vsIsCharNA))
    versionSpec[vsIsCharNA] <- NA
  verIsCharNA <- version %in% "NA"
  if (any(verIsCharNA))
    version[verIsCharNA] <- NA
  out <- Map(
    vers = version, ineq = inequality, verSpec = versionSpec, # this will recycle, which may be bad
    function(ineq, vers, verSpec) {
      if (!is.na(ineq) && !is.na(vers) && !is.na(verSpec)) {
        a <- compareVersion(verSpec, vers)
        out <- do.call(ineq, list(0, a))
      } else {
        NA
      }
    }
  )
  out <- unlist(out)
  out
}

updatePackagesWithNames <- function(pkgDT, packages) {
  origPackagesHaveNames <- nchar(names(packages)) > 0
  if (any(origPackagesHaveNames, na.rm = TRUE)) {
    whHasHEAD <- grep(HEADgrepWithParentheses, names(packages))
    packageFullNameWithHEAD <- packages[whHasHEAD]
    if (length(whHasHEAD)) {
      ## TODO: this should use `checkHEAD` rather than the custom handling
      ## below. Left as a note, not a stop(): unlike the two guards in pak.R,
      ## this browser() was not an error check -- it sat unconditionally on a
      ## live path, and browser() is a no-op in a non-interactive session, so
      ## this code runs and works for users today. Converting it to stop()
      ## would turn a working path into a hard error for anyone passing a named
      ## package with a HEAD spec.
      pkgDT[
        match(packages[origPackagesHaveNames], packageFullName),
        hasHEAD := packageFullName %in% packageFullNameWithHEAD #
      ]
      names(packages)[whHasHEAD] <- NA
      origPackagesHaveNames[whHasHEAD] <- FALSE # Dealt with in previous line
      if (all(is.na(names(packages)))) names(packages) <- NULL
    }
    if (!is.null(names(packages))) {
      pkgDT[
        match(packages[origPackagesHaveNames], packageFullName),
        Package := names(packages[origPackagesHaveNames])
      ]
    }
  }
  pkgDT
}


addNamesToPackageFullName <- function(packageFullName, Package) {
  gg <- extractPkgName(packageFullName)
  dif <- !Package %in% gg
  alternateNamedPkgs <- Package[dif]
  if (NROW(alternateNamedPkgs)) {
    names(packageFullName) <- rep("", length(packageFullName))
    names(packageFullName)[dif] <- Package[dif]
  }
  packageFullName
}

messageDownload <- function(pkgDT, numToDownload, fromWhere) {
  paste0(blue(
    " -- downloading ", numToDownload, " packages from ", fromWhere,
    ": ", paste(pkgDT[["Package"]], collapse = comma), " --"
  ))
}

messageForInstall <- function(startTime, toInstall, numPackages, verbose, numGroups) {
  # currentTime <- Sys.time()
  if (NROW(toInstall)) {
    # dft <- difftime(currentTime, startTime, units = "secs")
    installRange <- unique(c(toInstall$installOrder[1], tail(toInstall$installOrder, 1)))
    # timeLeft <- dft / installRange[1] * (numPackages - installRange[1] + 1)

    # lotsOfTimeLeft <- dft > 10
    # timeLeftAlt <- if (lotsOfTimeLeft) format(timeLeft, units = "auto", digits = 1) else "..."
    # estTimeFinish <- if (lotsOfTimeLeft) Sys.time() + timeLeft else "...calculating"
    pkgToReport <- paste(preparePkgNameToReport(toInstall[["Package"]], toInstall[["packageFullName"]]),
                         collapse = comma)
    Source <- ifelse(toInstall$installFrom %in% .txtLocal, .txtLocal, toInstall$repoLocation)
    if (!is.null(toInstall[["newLocalFile"]])) {
      Source <- ifelse(toInstall[["newLocalFile"]] %in% TRUE, toInstall$repoLocation, toInstall[["installFrom"]])
    }
    pkgToReportBySource <- split(toInstall[["Package"]], Source)
    pkgFullNameToReportBySource <- split(toInstall[["packageFullName"]], Source)
    installRangeCh <- paste(installRange, collapse = ":")

    srces <- names(pkgToReportBySource)
    messageVerbose("  -- To install from:", verbose = verbose)
    nxtSrc <- c(yellow = .txtLocal, blue = "CRAN", turquoise = .txtArchive, green = .txtGitHub, black = "RSPM")
    Map(colr = names(nxtSrc), type = nxtSrc, function(colr, type) {
      pp <- pkgToReportBySource[[type]]
      if (type %in% srces) {
        if (type %in% .txtGitHub) {
          pp <- pkgFullNameToReportBySource[[type]]
        }
        mess <- paste0("  -- ", type, ": ", paste(pp, collapse = comma))
        mess <- paste0WithLineFeed(mess)
        messageVerbose(get(colr)(mess), verbose = verbose)
      }
    })
    messageVerbose(
      blue(
        "  -- ", installRangeCh, " of ", numPackages,
        if (numGroups > 1) {
          paste0(" (grp ", unique(toInstall$installSafeGroups), " of ", numGroups, ")")
        } else {
          ""
        } # ,
        # ". Estimated time left: ",
        # timeLeftAlt, "; est. finish: ", estTimeFinish
      ),
      verbose = verbose, verboseLevel = 0
    )
  }
}

#' Create a custom "available.packages" object
#'
#' This is the mechanism by which `install.packages` determines which packages
#' should be installed from where. With this override, we can indicate arbitrary
#' `repos`, `Package`, `File` for each individual package.
#' @param toInstall A `pkgDT` object
#' @inheritParams Require
availablePackagesOverride <- function(toInstall, repos, purge, type = getOption("pkgType"),
                                      verbose = getOption("Require.verbose")) {
  whLocal <- startsWith(unique(dirname(dirname(toInstall$Repository))), "file")
  if (any(whLocal %in% FALSE) && any(toInstall$repoLocation %in% "CRAN") &&
      !any(grepl("contrib", toInstall$Repository))) {
    repos <- unique(dirname(dirname(toInstall$Repository)))
  }

  if (!is.null(toInstall$Additional_repositories)) {
    repos <- c(repos, na.omit(toInstall$Additional_repositories))
  }
  ap <- available.packagesCached(repos = repos, purge = purge, returnDataTable = FALSE, type = type)
  pkgsNotInAP <- toInstall[["Package"]][!toInstall[["Package"]] %in% ap[, "Package"]]
  NumPkgsNotInAP <- length(pkgsNotInAP)

  if (NumPkgsNotInAP) { # basically no version is there

    # Have to make the ap object be the same length as the number of pkgsNotInAP
    if (NROW(ap) < NumPkgsNotInAP) { # no nough rows; must add
      ap3 <- matrix(nrow = NumPkgsNotInAP, rep(rep(NA, NCOL(ap)), NumPkgsNotInAP))
      colnames(ap3) <- colnames(ap)
    } else { #  too many rows, keep first NumPkgsNotInAP
      ap3 <- ap[seq(NumPkgsNotInAP), , drop = FALSE]
    }

    ap3[, "Package"] <- pkgsNotInAP
    rownames(ap3) <- ap3[, "Package"]
    ap3[, "Version"] <- toInstall[Package %in% pkgsNotInAP][["VersionOnRepos"]]
    if (!is.null(toInstall$Repository)) {
      ap3[, "Repository"] <- toInstall[Package %in% pkgsNotInAP]$Repository
    }
    ap3[, "Depends"] <- NA
    deps <- pkgDep(toInstall[Package %in% pkgsNotInAP][["packageFullName"]],
                   recursive = TRUE, repos = repos,
                   verbose = verbose)
    pkgHasNameDiffrntThanRepo <- extractPkgName(names(deps)) != toInstall[Package %in% pkgsNotInAP][["Package"]]
    if (any(pkgHasNameDiffrntThanRepo)) {
      names(deps)[pkgHasNameDiffrntThanRepo] <- toInstall[Package %in% pkgsNotInAP][["Package"]][pkgHasNameDiffrntThanRepo]
    }
    deps2 <- unlist(Map(dep = deps, nam = names(deps), function(dep, nam) {
      paste(setdiff(extractPkgName(dep), extractPkgName(nam)), collapse = comma)
    })) # -1 is "drop self"
    ap3[match(extractPkgName(names(deps2)), ap3[, "Package"]), "Imports"] <- deps2
    ap3[, "Suggests"] <- NA
    rownames(ap3) <- pkgsNotInAP
  } else {
    ap3 <- ap[0, , drop = FALSE]
  }

  ap <- ap[ap[, "Package"] %in% toInstall[["Package"]], , drop = FALSE]
  if (NROW(ap3)) {
    ap <- rbind(ap, ap3)
  }

  toInstallList <- split(toInstall, by = "installFrom")
  apList <- list()
  apOrig <- ap
  for (i in names(toInstallList)) {
    # First do version number -- this is same for all locations
    whUpdate <- match(toInstallList[[i]][["Package"]], apOrig[, "Package"])
    ap <- apOrig[whUpdate, , drop = FALSE]
    isNA <- is.na(toInstallList[[i]][["VersionOnRepos"]])
    if (any(isNA)) {
      whUseRepository <- !toInstallList[[i]][isNA]$localFile %in% useRepository &
        !is.na(toInstallList[[i]][isNA]$localFile)
      if (any(whUseRepository)) {
        evn <- extractVersionNumber(filenames = toInstallList[[i]][isNA][whUseRepository]$localFile)
        ap[isNA, "Version"] <- evn
      }
    }
    if (any(!isNA)) {
      ap[!isNA, "Version"] <- toInstallList[[i]][["VersionOnRepos"]][!isNA]
    }
    if (i %in% c(.txtArchive, "CRAN")) {
      ap[, "Repository"] <- toInstallList[[i]]$Repository
    }
    if (i %in% c(.txtLocal, .txtGitHub)) {
      localFile2 <- toInstallList[[i]]$localFile
      fnBase <- basename(localFile2)

      # This is necessary for `clonePackages` ... install.packages doesn't care if Version in "Version" column
      #   doesn't match "File" in `available`
      versionFromFn <- extractVersionNumber(filenames = fnBase)
      needVersionUpdateFromLocalFile <- versionFromFn != ap[, "Version"]
      if (any(needVersionUpdateFromLocalFile %in% TRUE)) {
        ap[needVersionUpdateFromLocalFile, "Version"] <- versionFromFn[needVersionUpdateFromLocalFile]
      }

      fe <- file.exists(fnBase)
      if (any(!fe)) # now the files will be in tmpdir already
        file.copy(localFile2[!fe], fnBase[!fe], overwrite = TRUE) # copy it to "here"
      newNameWithoutSHA <- gsub("(-[[:alnum:]]{40})_", "_", fnBase)
      fileRenameOrMove(fnBase, newNameWithoutSHA)
      ap[, "File"] <- newNameWithoutSHA
      ap[, "Repository"] <-
        paste0("file:///", normPath("."))

      # CHECK correct Imports etc.
      whichNeedDepsOverride <- which(toInstall$availableVersionOKthisOne %in% FALSE)
      pks <- toInstall$Package[whichNeedDepsOverride]
      colsHere <- intersect(colsOfDeps, colnames(ap))
      ll <- Map(jj = colsHere, function(jj)
        lapply(toInstall[[jj]][toInstall$Package %in% pks], paste, collapse = ", "))
      for (ch in colsHere)
        ap[ap[, "Package"] %in% pks, ch] <- unlist(ll[[ch]])

    }
    apList[[i]] <- ap
  }

  do.call(rbind, apList)
}

useRepository <- "useRepository"

setNcpus <- function() {
  Ncpus <- getOption("Ncpus")
  if (is.null(Ncpus)) {
    newVal <- if (requireNamespace("parallel", quietly = TRUE)) {
      min(8, parallel::detectCores())
    } else {
      4
    }

    opts <- options(Ncpus = newVal)
  } else {
    opts <- NULL
  }
  opts
}

keepOnlyBinary <- function(fn, keepSourceIfOnlyOne = TRUE) {
  if (length(fn) > 1) {
    isBin <- isBinary(fn, needRepoCheck = FALSE)
    if (any(isBin)) { # pick binary if it exists; faster to install
      versions <- extractVersionNumber(filenames = fn)
      pkgs <- extractPkgName(filenames = basename(fn))
      pkgVer <- paste0(pkgs, "_", versions)
      N <- table(pkgVer)
      pkgVerKeep <- names(N)[N == 1]
      pkgVerDups <- names(N)[N > 1]
      if (keepSourceIfOnlyOne %in% FALSE) {
        fn <- fn[isBin]
      } else {
        fnKeepSingles <- fn[match(pkgVerKeep, pkgVer)]
        fnMoreThanOne <- fn[pkgVer %in% pkgVerDups]
        fnMoreThanOne <- fnMoreThanOne[isBinary(fnMoreThanOne, needRepoCheck = FALSE)]
        fn <- c(fnMoreThanOne, fnKeepSingles)
      }
    }
  }
  fn
}

moveFileToCacheOrTmp <- function(pkgInstall) {
  ## Legacy non-pak downloader: park downloaded tarballs alongside Require's
  ## bookkeeping rather than in pak's tarball cache (pak doesn't index files
  ## dropped here externally, so they'd be invisible anyway).
  localFileDir <- if (nzchar(.requirePkgInfoDir())) {
    .requirePkgInfoDir(create = TRUE)
  } else {
    tempdir3()
  }
  fns <- pkgInstall$localFile
  movedFilename <- file.path(localFileDir, basename(fns))
  if (!identical(fns, movedFilename)) {
    fileRenameOrMove(fns, movedFilename)
    set(pkgInstall, NULL, "localFile", movedFilename)
  }
  pkgInstall
}

getGitHubVersionOnRepos <- function(pkgGitHub) {
  notYet <- is.na(pkgGitHub[["VersionOnRepos"]])
  if (any(notYet)) {
    pkgGitHub <- dlGitHubFile(pkgGitHub)
    dFile <- pkgGitHub[["DESCFile"]]
    hasDFile <- which(!is.na(dFile))
    fesLenDFile <- file.exists(pkgGitHub$DESCFile[hasDFile])
    if (any(!fesLenDFile)) {
      set(pkgGitHub, hasDFile[!fesLenDFile], "DESCFile", "")
      # pkgGitHub[hasDFile[!fesLenDFile], DESCFile := ""]
    }
    if (any(fesLenDFile)) {
      set(pkgGitHub, hasDFile[fesLenDFile], "VersionOnRepos",  DESCRIPTIONFileVersionV(dFile[hasDFile[fesLenDFile]]))
      mayNeedPackageNameChange <- DESCRIPTIONFileOtherV(dFile[hasDFile[fesLenDFile]], other = "Package")
      alreadyCorrect <- pkgGitHub[["Package"]][hasDFile[fesLenDFile]] == mayNeedPackageNameChange
      notAlreadyCorrect <- alreadyCorrect %in% FALSE
      if (any(notAlreadyCorrect)) {
        set(pkgGitHub, hasDFile[fesLenDFile][notAlreadyCorrect], "Package",
            mayNeedPackageNameChange[notAlreadyCorrect])
      }
    }
    # pkgGitHub[!is.na(DESCFile), VersionOnRepos := DESCRIPTIONFileVersionV(DESCFile)]
  }
  # }
  pkgGitHub
}

#' @importFrom utils tail
localFileID <- function(Package, localFiles, repoLocation, SHAonGH, inequality,
                        VersionOnRepos, versionSpec, verbose = getOption("Require.verbose")) {
  ##### Not vectorized ######
  PackagePattern <- paste0("^", Package, "(\\_|\\-)+.*")
  whLocalFile <- grep(pattern = PackagePattern, x = basename(localFiles))
  fn <- localFiles[whLocalFile]
  systemSpecificFileTypes <- if (isWindows()) {
    endsWith(fn, "zip") | endsWith(fn, "tar.gz")
  } else if (isMacOS()) {
    endsWith(fn, macBinaryFileExtNoDot) | endsWith(fn, "tar.gz")
  } else {
    endsWith(fn, "tar.gz")
  }
  fn <- fn[systemSpecificFileTypes]

  if (repoLocation %in% .txtGitHub) {
    if (grepl("Error in file", SHAonGH) && isTRUE(getOption("Require.offlineMode"))) {
      messageVerbose("Using Require.offlineMode; could not identify SHA on Github for ",
                     green(Package), "; using the latest version that exists locally, ",
                     "which may not be a GitHub version", verbose = verbose)
      fn <- fn[order(package_version(extractVersionNumber(filenames = fn)), decreasing = TRUE)]
    } else {
      fn <- if (is.na(SHAonGH)) "" else grep(SHAonGH, fn, value = TRUE)
    }
    fn <- keepOnlyBinary(fn)
  } else {
    if (length(fn)) {
      fn <- keepOnlyBinary(fn)
      localVer <- extractVersionNumber(filenames = fn) # there may be >1 file for a given package; take
      if (is.na(inequality)) { # need max --> which will be the versionSpec; so see if localVer is same as versionSpec
        if (!is.na(VersionOnRepos)) {
          ord <- order(c(package_version(localVer), package_version(VersionOnRepos)), decreasing = TRUE) # local first
        } else {
          ord <- 1
        }

        if (ord[1] %in% seq_along(localVer)) { # if it is first, then it is bigger or equal, so keep
          fn <- fn[ord[1]]
        } else {
          fn <- ""
        }
      } else {
        isHEAD <- identical(versionSpec, "HEAD")
        if (isHEAD) {
          keepLoc <- try(unlist(compareVersion2(localVer[isHEAD], VersionOnRepos[isHEAD], "==")))
        } else {
          keepLoc <- try(unlist(compareVersion2(localVer[!isHEAD], versionSpec[!isHEAD], inequality[!isHEAD])))
        }
        if (is(keepLoc, "try-error")) {
          browserDeveloper("Error 978; please contact developer")
        }
        if (!identical(inequality, "==") && !is.na(VersionOnRepos) && !isHEAD) {
          keepRep <- compareVersion2(VersionOnRepos, versionSpec, inequality)
          if (any(keepLoc %in% TRUE)) { # local has at least 1 that is good -- could be many versions of the package locally
            locPacVer <- package_version(localVer[keepLoc]) # keep all that pass version check
            keepLoc <- which(keepLoc)[which(max(locPacVer) == locPacVer)][1] # take the one that passes version check that is max version
            if (any(keepRep %in% TRUE)) { # remote has as at least 1 that is good # next, take local if local is same or newer than remote
              ord <- order(c(package_version(localVer[keepLoc]), package_version(VersionOnRepos)), decreasing = TRUE) # local first
              if (ord[1] %in% seq_along(localVer[keepLoc])) { # if it is first, then it is bigger or equal, so keep
                fn <- fn[keepLoc][ord[1]]
              } else {
                fn <- ""
              }
            } else {
              fn <- fn[keepLoc]
            }
          } else {
            fn <- ""
          }
        } else {
          fn <- tail(fn[keepLoc], 1) # keepLoc is possibly length > 1
        }
      }
    } else {
      fn <- ""
    }
  }
  # } else {
  #   order(package_version(extractVersionNumber(filenames = fn)), decreasing = TRUE)
  # }
  if (length(fn) > 1) {
    isGH <- isGitHub(filenames = fn)
    if (any(!isGH)) {
      fn <- fn[!isGH]
    } else {
      fn <- fn[1]
    }
  }
  if (length(fn) == 0) fn <- ""
  fn
}

identifyLocalFiles <- function(pkgInstall, repos, purge, libPaths, verbose) {
  #### Uses pkgInstall #####
  if (nzchar(.requirePkgInfoDir())) {
    removeOldFlatCachePkgs(verbose = verbose)
    # check for crancache copies -- only look in repos-specific subdirectories
    repoDirs <- cachePkgDirForRepo(repos)
    repoDirs <- repoDirs[dir.exists(repoDirs)]
    localFiles <- if (length(repoDirs) > 0) {
      lf <- unlist(lapply(repoDirs, dir, full.names = TRUE))
      lf[!dir.exists(lf)]  # exclude subdirectories (e.g., metadata dirs like source/, binary/)
    } else {
      character(0)
    }
    localFiles <- doCranCacheCheck(localFiles, verbose)
    pkgInstall <- localFilename(pkgInstall, localFiles, libPaths = libPaths, verbose = verbose)
    if (isTRUE(getOption("Require.offlineMode"))) {
      noneFound <- nchar(pkgInstall$localFile) == 0
      if (isTRUE(any(noneFound))) {
        pkgInstall[noneFound]
      }
    }
    pkgInstall[, haveLocal :=
                 unlist(lapply(localFile, function(x) c(.txtNoLocal, .txtLocal)[isTRUE(nchar(x) > 0) + 1]))]
    pkgInstall[haveLocal %in% .txtLocal, `:=`(
      installFrom = haveLocal,
      availableVersionOK = TRUE,
      Repository = paste0("file:///", dirname(localFile))
    )]
  } else {
    set(pkgInstall, NULL, c("localFile"), "")
  }
}


confirmEqualsDontViolateInequalitiesThenTrim <- function(pkgDT,
                                                         ifViolation = c("removeEquals", "stop"),
                                                         verbose = getOption("Require.verbose")) {
  # Basically, if the package has no version specification, it can take "any version", so don't use it
  #   at all, unless it is the only description
  set(pkgDT, NULL, "hasInequality", !is.na(pkgDT[["inequality"]]))
  pp <- pkgDT[, keep44 := if (any(hasInequality)) ifelse(hasInequality, .I, NA) else .I, by = "Package"]
  pkgDT <- pp[unique(na.omit(keep44))]
  if (!is.null(pkgDT[["parentPackage"]])) {
    pkgDT[, needKeep := grepl("\\(", parentPackage) | is.na(parentPackage)]
    pkgDT[, keep55 := if (any(needKeep)) ifelse(needKeep | is.na(parentPackage), .I, NA) else .I, by = "Package"]
    pkgDT <- pkgDT[unique(na.omit(keep55))]
  }


  set(pkgDT, NULL, "isEquals", pkgDT[["inequality"]] == "==")
  pkgDT[,  oppositeInequals := any(grepl("<", inequality)) & any(grepl(">", inequality)), by = "Package"]
  pkgDT[, hasEqualsAndInequals := any(isEquals %in% TRUE) && any(isEquals %in% FALSE), by = "Package"]

  if (any(pkgDT$hasEqualsAndInequals)) {
    pkgDT[hasEqualsAndInequals %in% TRUE, EqualsDoesntViolate := {
      out <- rep(NA, length.out = .N)
      wh <- which(isEquals)
      whNot <- which(!isEquals)
      if (length(wh)) {
        out[wh] <- try(unlist(Map(verSpec = versionSpec[wh], function(verSpec) {
          all(compareVersion2(verSpec, versionSpec[whNot], inequality[whNot]))
        })))
        if (is(out, "try-error")) {
          browserDeveloper("Error 844; please contact developer")
        }
        out
      }
      out
    },
    by = "Package"
    ]
    set(pkgDT, which(pkgDT$isEquals %in% TRUE & pkgDT$hasEqualsAndInequals %in% FALSE), "EqualsDoesntViolate", TRUE)
    pkgDT[, violation := if (any(isEquals %in% TRUE) && all(!EqualsDoesntViolate %in% TRUE)) {
      TRUE
    } else {
      FALSE
    },
    by = "Package"
    ]

    set(pkgDT, NULL, "isGT", pkgDT[["inequality"]] == ">")
    pkgDT[, hasGTAndInequals := any(isGT %in% TRUE) && any(isGT %in% FALSE), by = "Package"]
    pkgDT[hasGTAndInequals %in% TRUE, GTDoesntViolate := {
      compareVersion3(.N, isGT, versionSpec, inequality)
      # out <- rep(NA, length = .N)
      # wh <- which(isGT)
      # whNot <- which(!isGT)
      # if (length(wh)) {
      #   out[wh] <- try(unlist(Map(verSpec = versionSpec[wh], function(verSpec) {
      #     withCallingHandlers(
      #       all(compareVersion2(versionSpec[whNot], verSpec, inequality[wh]))
      #       , warning = function(w)
      #     )
      #   })))
      #   if (is(out, "try-error")) {
      #     browserDeveloper("Error 844; please contact developer")
      #   }
      #   out
      # }
      # out
    },
    by = "Package"
    ]

    pkgDT[, violation2 := if (any(isGT %in% TRUE) && all(GTDoesntViolate %in% FALSE)) {
      TRUE
    } else {
      FALSE
    },
    by = "Package"
    ]

    if (any(pkgDT$violation %in% TRUE)) {
      messageVerbose(green(
        .txtFollowingPkgsVersionCantBeMet,
        "keeping the newer version: "
      ), verbose = verbose, verboseLevel = 1)
      cols <- c("Package", "packageFullName", "versionSpec")
      cols2 <- setdiff(cols, "packageFullName")
      cols3 <- c(cols, "versionToKeep")
      violationsDF <- pkgDT[violation %in% TRUE & !is.na(versionSpec), ..cols]
      # can't use setorderv on versions b/c stored as character vector
      orderVersions <- order(violationsDF[[cols2[1]]],
                             package_version(violationsDF[[cols2[[2]]]]),
                             decreasing = TRUE)
      violationsDF <- violationsDF[orderVersions,]
      violationsDF <- violationsDF[, versionToKeep := {
        ver <- rep("", length = .N)
        ver[1] <- versionSpec[1]
        ver
      }, by = "Package"][, ..cols3]
      assign("violations", violationsDF, envir = pkgEnv())
      messageDF(verbose = verbose, verboseLevel = 1, violationsDF)
      if (grepl("remove|rm", ifViolation[1])) {
        keepCols2 <- pkgDT$violation %in% TRUE & !pkgDT[["inequality"]] %in% "==" | pkgDT$violation %in% FALSE
        rm <- pkgDT[which(!keepCols2)]
        pkgDT <- pkgDT[which(keepCols2)]
        pkgDT[Package %in% rm[["Package"]], installResult := msgPackageViolation]
        ## When we rejected an exact `==X` pin in favour of a `>=Y` / `>Y`,
        ## narrow the surviving constraint to `==Y` (the minimum that
        ## satisfies the surviving inequality) instead of latest. Honours
        ## the user intent that motivated the `==` pin: "as close to X as
        ## we can get while still satisfying everyone else's lower bound."
        .pinSurvivorToMinimumAfterExactReject(pkgDT, rm)
      }
    }

    if (any(pkgDT$violation2 %in% TRUE)) {
      messageVerbose(green(
        .txtFollowingPkgsVersionCantBeMet,
        "keeping the newer version: "
      ), verbose = verbose, verboseLevel = 1)
      cols <- c("Package", "packageFullName", "versionSpec")
      cols2 <- setdiff(cols, "packageFullName")
      cols3 <- c(cols, "versionToKeep")
      violationsDF <- pkgDT[violation2 %in% TRUE & !is.na(versionSpec), ..cols]
      # can't use setorderv on versions b/c stored as character vector
      orderVersions <- order(violationsDF[[cols2[1]]],
                             package_version(violationsDF[[cols2[[2]]]]),
                             decreasing = TRUE)
      violationsDF <- violationsDF[orderVersions,]
      violationsDF <- violationsDF[, versionToKeep := {
        ver <- rep("", length = .N)
        ver[1] <- versionSpec[1]
        ver
      }, by = "Package"][, ..cols3]
      messageDF(verbose = verbose, verboseLevel = 1, violationsDF)
      if (grepl("remove|rm", ifViolation[1])) {
        ## Capture the rejected `==` rows BEFORE filtering so we can pin
        ## survivors to the minimum that satisfies the surviving
        ## inequality. Symmetric with the violation block above.
        rm <- pkgDT[!(violation2 %in% TRUE & !inequality %in% "==" | violation2 %in% FALSE)]
        pkgDT <- pkgDT[violation2 %in% TRUE & !inequality %in% "==" | violation2 %in% FALSE]
        .pinSurvivorToMinimumAfterExactReject(pkgDT, rm)
      }
    }

    pkgDT[, keepCols3 := if (any(isEquals %in% TRUE) && any(EqualsDoesntViolate %in% TRUE)) {
      .I[isEquals %in% TRUE & EqualsDoesntViolate %in% TRUE][1]
    } else {
      .I
    },
    by = "Package"
    ]

    pkgDT <- pkgDT[unique(keepCols3)]

    # get rid of the GT if they are TRUE
    pkgDT[, remove := (isGT %in% TRUE & GTDoesntViolate %in% TRUE)]
    if (any(pkgDT$remove))
      pkgDT <- pkgDT[!remove %in% TRUE]


    set(pkgDT, NULL, c("isEquals", "hasEqualsAndInequals", "EqualsDoesntViolate", "keepCols3"), NULL)
  }

  if (any(pkgDT$oppositeInequals)) {
    whOppositeInequals <- which(pkgDT$oppositeInequals)
    pkgDT[whOppositeInequals,
          violationsDoubleInequals := detectDoubleInequalsViolations(inequality, versionSpec, .N)
          , by = "Package"]
    if (grepl("remove|rm", ifViolation[1]) && any(pkgDT$violationsDoubleInequals[whOppositeInequals])) {
      set(pkgDT, NULL, "keepCols4", TRUE)
      pkgDT[whOppositeInequals, keepCols4 := !grepl("<", pkgDT[["inequality"]][whOppositeInequals]), by = "Package"]
      rm <- pkgDT[which(!keepCols4)]
      pkgDT <- pkgDT[which(keepCols4)]
      pkgDT[Package %in% rm[["Package"]], installResult := msgPackageViolation]
      set(pkgDT, NULL, c("violationsDoubleInequals", "keepCols4"), NULL)
    }

  }
  pkgDT
}

# ---------------------------------------------------------------------------
# .pinSurvivorToMinimumAfterExactReject: called from
# confirmEqualsDontViolateInequalitiesThenTrim after we've dropped a
# `==X` row because some other constraint (typically `>=Y` with Y > X)
# can't be reconciled. For each Package whose `==X` got rejected this
# way, rewrite the surviving `>=Y`/`>Y` row(s) as `==Y` (mutates
# `pkgDT` in place, including `packageFullName` so downstream pak ref
# construction picks up the pin).
#
# Why: the rejected `==X` represented user intent ("as old as
# possible"). Latching the surviving floor to its minimum keeps as
# close to that intent as the surviving constraint allows, instead
# of letting pak fetch CRAN's latest. End-to-end driver: user listed
# `c("stringfish (==0.17.0)", ...)` while another package required
# `stringfish (>= 0.18.0)`; previous behaviour kept `>= 0.18.0` and
# pak installed `0.19.0`. Now we pin to `== 0.18.0`.
# ---------------------------------------------------------------------------
.pinSurvivorToMinimumAfterExactReject <- function(pkgDT, rm) {
  if (!is.data.table(rm) || !NROW(rm)) return(invisible(pkgDT))
  if (!all(c("Package", "inequality") %in% names(rm))) return(invisible(pkgDT))
  rejectedExactPkgs <- rm[inequality %in% "==", unique(Package)]
  if (!length(rejectedExactPkgs)) return(invisible(pkgDT))
  toPin <- pkgDT$Package %in% rejectedExactPkgs &
           pkgDT$inequality %in% c(">=", ">") &
           !is.na(pkgDT$versionSpec) &
           nzchar(pkgDT$versionSpec)
  if (!any(toPin)) return(invisible(pkgDT))
  pkgDT[which(toPin), `:=`(
    inequality = "==",
    packageFullName = paste0(Package, " (== ", versionSpec, ")")
  )]
  invisible(pkgDT)
}

detectDoubleInequalsViolations <- function(inequality, versionSpec, N) {
  {
    res <- logical(N)
    whGT <- grep(">", inequality)
    whLT <- grep("<", inequality)
    res[whLT] <- mapply(whL = whLT, function(whL) {
      all(compareVersion2(rep(versionSpec[whL], length(whGT)), versionSpec[whGT], inequality[whGT]))
    })
    res[whGT] <- mapply(whG = whGT, function(whG)
      compareVersion2(rep(versionSpec[whG], length(whLT)), versionSpec[whLT], inequality[whLT]))
    return(!res)
  }
}

compareVersion3 <- function(.N, isGT, versionSpec, inequality) {
  out <- rep(NA, length = .N)
  wh <- which(isGT)
  whNot <- which(!isGT)
  if (length(wh)) {
    out[wh] <-
      unlist(
        Map(verSpec = versionSpec[wh], function(verSpec) {
          all(compareVersion2(versionSpec[whNot], verSpec, inequality[whNot]))
        })
      )
    if (is(out[wh], "try-error")) {
      browserDeveloper("Error 844; please contact developer")
    }
    out
  }
  out
}

keepOnlyGitHubAtLines <- function(pkgDT, verbose = getOption("Require.verbose")) {
  gitRepos <- pkgDT$repoLocation %in% .txtGitHub
  if (any(gitRepos)) {
    pkgDT[gitRepos %in% TRUE, c("versionSpec", "inequality") := {
      vs <- versionSpec
      ineq <- inequality
      whHasSHAasBranch <- nchar(Branch) == 40
      if (any(whHasSHAasBranch, na.rm = TRUE)) {
        wh <- which(whHasSHAasBranch %in% TRUE)
        vs[wh] <- VersionOnRepos[wh]
        ineq[wh] <- "=="
      }
      list(vs, ineq)
    }, by = "Package"]
    pkgDT <- confirmEqualsDontViolateInequalitiesThenTrim(pkgDT, verbose = verbose)
  }
  pkgDT
}

#' Collapse redundant package specifications
#'
#' Given package specifications that may name the same package more than once --
#' e.g. `"pkg"`, `"pkg (>= 1.0)"` and `"user/pkg@branch"` -- reduce them to one
#' entry per package, keeping the most specific requirement. This is the
#' reduction `Require()` applies to a dependency set before installing, exposed
#' because packages that assemble their own `reqdPkgs`-style lists need the same
#' rule to agree with what `Require()` will actually do.
#'
#' @param pkgInstall Package specifications: a character vector, or a
#'   `data.table` as produced internally by `Require`.
#' @param repos,purge,libPaths Unused; retained so existing positional calls
#'   keep working.
#' @param verbose Numeric or logical, controlling messaging verbosity.
#' @param type Package type, as in [utils::install.packages()].
#'
#' @return A `data.table` with one row per package, with redundant entries
#'   removed. Callers wanting the specifications back as text take the
#'   `packageFullName` column.
#'
#' @family version specifications
#' @export
#' @examples
#' trimRedundancies(c("data.table", "data.table (>= 1.14.0)"))
#'
#' @importFrom data.table rleid
trimRedundancies <- function(pkgInstall, repos = NULL, purge = NULL, libPaths = NULL,
                             verbose = getOption("Require.verbose"),
                             type = getOption("pkgType")) {
  if (!is.data.table(pkgInstall)) {
    pkgInstall <- toPkgDT(pkgInstall)
    pkgInstall <- parsePackageFullname(pkgInstall)
  }
  if (NROW(pkgInstall)) {
    pkgInstall <- removeDups(pkgInstall)
    if (anyDuplicated(pkgInstall[["Package"]])) {
      if (!is.null(pkgInstall[[hasHEADtxt]])) {
        hasHeadRows <- which(pkgInstall[[hasHEADtxt]] %in% TRUE)
        whToRM <- which(pkgInstall[["Package"]] %in% pkgInstall[hasHeadRows][["Package"]])
        whToRM <- setdiff(whToRM, hasHeadRows)
        if (length(whToRM))
          pkgInstall <- pkgInstall[-whToRM]
      }

      versionSpecNotNA <- !is.na(pkgInstall$versionSpec)
      if (any(versionSpecNotNA)) {
        verSpec <- pkgInstall$versionSpec[versionSpecNotNA]
        isHEAD <- verSpec %in% "HEAD"
        verSpec[isHEAD] <- "0.0.0"
        ord <- order(package_version(verSpec),
                     decreasing = TRUE, na.last = TRUE
        ) # can't use setorderv because data.table can't sort on package_version class
      } else {
        ord <- seq(NROW(pkgInstall))
      }

      pkgInstallTmp <- list()
      if (any(versionSpecNotNA)) {
        pkgInstallTmp[[1]] <- pkgInstall[versionSpecNotNA][ord]
      } # use the order to reorder them. It is not sort key.
      if (any(!versionSpecNotNA)) {
        pkgInstallTmp[[2]] <- pkgInstall[versionSpecNotNA %in% FALSE]
      }
      pkgInstall <- rbindlist(pkgInstallTmp)
      # set(pkgInstall, NULL, "versionSpecGroup", data.table::rleid(pkgInstall$versionSpec))
      setOrderOn <- list(colm = c("Package", # "versionSpecGroup",
                                  "inequality", "repoLocation"),
                         ordr = c(1L, # 1L,
                                  -1L, 1L))
      setDT(setOrderOn)
      setOrderOn <- setOrderOn[setOrderOn$colm %in% colnames(pkgInstall)]
      setorderv(pkgInstall, setOrderOn$colm, #c("Package", "versionSpecGroup", "inequality", "repoLocation"),
                order = setOrderOn$ordr, na.last = TRUE)

      pkgAndInequality <-
        intersect(colnames(pkgInstall), c("Package", "inequality", "repoLocation"))#, "versionSpecGroup")

      pkgInstall[, keepBasedOnRedundantInequalities :=
                   unlist(lapply(.I, function(ind) {
                     ifelse(is.na(inequality), ind,
                            ifelse(inequality == ">=", .I[1], ifelse(inequality == "<=", tail(.I, 1), .I))
                     )
                   })),
                 by = pkgAndInequality
      ]

      pkgInstall <- pkgInstall[unique(keepBasedOnRedundantInequalities)]
      set(pkgInstall, NULL, "keepBasedOnRedundantInequalities", NULL)
      pkgInstall <- confirmEqualsDontViolateInequalitiesThenTrim(pkgInstall)

      if (any(pkgInstall[["inequality"]] %in% c(">", "<"))) {
        pkgInstall[, keepBasedOnRedundantInequalities :=
                     unlist(lapply(.I, function(ind) {
                       ifelse(is.na(inequality), ind,
                              ifelse(inequality == ">", .I[1], ifelse(inequality == "<", tail(.I, 1), .I))
                       )
                     })),
                   by = pkgAndInequality
        ]
        pkgInstall <- pkgInstall[unique(keepBasedOnRedundantInequalities)]
        set(pkgInstall, NULL, "keepBasedOnRedundantInequalities", NULL)
        pkgInstall <- confirmEqualsDontViolateInequalitiesThenTrim(pkgInstall)

      }
      pkgInstall <- trimRedundantVersionAndNoVersion(pkgInstall)
      # pkgInstall <- removeDups(pkgInstall)
    }
  }
  pkgInstall[]
}

trimRedundantVersionAndNoVersion <- function(pkgInstall) {
  set(pkgInstall, NULL, "hasVers", !is.na(pkgInstall$versionSpec))
  hasV <- pkgInstall$hasVers == TRUE
  pkgInstall[, `:=`(
    atLeastOneWithVersionSpec = any(hasVers)
    # Current = all(Current == TRUE)
  ), by = "Package"]
  pkgInstall <-
    pkgInstall[!(pkgInstall$atLeastOneWithVersionSpec == TRUE &
                   pkgInstall$hasVers == FALSE)] # remove cases where no version spec >1 case
}


checkAvailableVersions <- function(pkgInstall, repos, purge, libPaths, verbose = getOption("Require.verbose"),
                                   type = getOption("pkgType")) {

  pkgInstallTmp <- getVersionOnRepos(pkgInstall, repos, purge, libPaths, type = type)
  pkgInstall <- pkgInstallTmp
  # coming out of getVersionOnRepos, will be some with bin and src on windows; possibly different versions; take only first, if identical URL at top level
  #  https://cran.rstudio.com/bin/windows/contrib/4.3 should be same Repo as https://cran.rstudio.com/src/contrib
  set(pkgInstall, NULL, "RepositoryTop", strsplit(gsub("http://|https://", "", pkgInstall$Repository), "/")[[c(1, 1)]])
  pkgInstall <- availableVersionOK(pkgInstall)

  pkgInstall <- updateReposForSrcPkgs(pkgInstall, verbose = verbose)

  # This next line removes the duplicates coming from available.packages ... so it will be too late
  #  after this line to "updateReposForSrcPkgs" further downstream; needs to be previous line
  pkgInstall <-
    rbindlist(list(
      pkgInstall[!availableVersionOK %in% TRUE],
      unique(pkgInstall[availableVersionOK %in% TRUE], by = c("Package", "VersionOnRepos", "RepositoryTop"))
    ))
  set(pkgInstall, NULL, "RepositoryTop", NULL)
  pkgInstall <- keepOnlyGitHubAtLines(pkgInstall, verbose = verbose)
  pkgInstall[, binOrSrc := binOrSrc(grepl("\\<bin\\>", Repository))]
  # pkgInstall[, binOrSrc := c("src", "bin")[grepl("\\<bin\\>", Repository) + 1]]
  setorderv(pkgInstall, c("Package", "availableVersionOKthisOne", "binOrSrc"), order = c(1L, -1L, 1L)) # OK = TRUE first, bin before src

  pkgInstall[, keepCols5 := {
    # This will pick the one that is OK, or if they are all NA (meaning no version spec),
    #    or all FALSE (meaning need to try Archive) <-- this was removed because can have 2 repositories, e.g., predictiveecology.r-universe.dev and CRAN MUST TRY BOTH
    #    then also pick first one that OK
    ok <- any(availableVersionOKthisOne %in% TRUE) || all(is.na(availableVersionOKthisOne)) # || all(availableVersionOKthisOne %in% FALSE)
    if (ok) .I[ok][1] else .I
  }, by = c("Package")]
  pkgInstall <- pkgInstall[unique(keepCols5)]

  # next line can switch a "install" to "dontInstall"
  pkgInstall <- removeHEADpkgsIfNoUpdateNeeded(pkgInstall, verbose = verbose)
  pkgInstallList <- split(pkgInstall, by = "needInstall")

  if (NROW(pkgInstallList[[.txtInstall]])) {
    pkgInstallTmp  <- pkgInstallList[[.txtInstall]] # pointer only
    # pkgInstallTmp <- pkgInstall
    if (any(!pkgInstallTmp$availableVersionOK)) {
      if (!is.null(pkgInstallTmp$Additional_repositories)) {
        pkgInstallAvails <- split(pkgInstallTmp, pkgInstallTmp$availableVersionOK)
        if (any(!is.na(pkgInstallAvails[["FALSE"]]$Additional_repositories))) {
          pkgAddRep <- split(pkgInstallAvails[["FALSE"]], pkgInstallAvails[["FALSE"]]$Additional_repositories)
          pkgAddRepTmp <- Map(repo = names(pkgAddRep), pkgAr = pkgAddRep, function(repo, pkgAr) {
            pkgArTmp <- getVersionOnRepos(pkgAr, repo, purge, libPaths, type = type)
          })
          pkgAddRepTmp <- rbindlist(pkgAddRepTmp)
          pkgInstallTmp2 <- pkgInstallTmp[, c("packageFullName", "tmpOrder", "Additional_repositories")]
          pkgInstallTmp2 <- unique(pkgInstallTmp2, by = c("packageFullName"))
          pkgAddRep <- pkgInstallTmp2[!is.na(Additional_repositories)][pkgAddRepTmp, on = "packageFullName"] # , allow.cartesian = TRUE]
          set(pkgAddRep, NULL, intersect("i.tmpOrder", colnames(pkgAddRep)), NULL)
          pkgInstallTmp <- pkgInstallTmp[is.na(Additional_repositories)] # keep this for end of this chunk
          pkgInstallAvails[["FALSE"]] <- rbindlist(list(pkgInstallTmp, pkgAddRep), fill = TRUE, use.names = TRUE)
          setorderv(pkgInstallAvails[["FALSE"]], "tmpOrder")
        }
        pkgInstallList[[.txtInstall]] <- rbindlist(pkgInstallAvails, fill = TRUE, use.names = TRUE)
      }

    }

    set(pkgInstallList[[.txtInstall]], NULL, "keepCols5", NULL)
  }
  pkgInstall <- rbindlist(pkgInstallList, fill = TRUE, use.names = TRUE)

  whRmImports <- which(pkgInstall$availableVersionOK %in% FALSE)
  if (length(whRmImports)) {
    set(pkgInstall, whRmImports,
        intersect(colnames(pkgInstall), c("Imports", "Depends", "LinkingTo", "Suggests")),
        NA)
  }

  pkgInstall
}

updateInstallSafeGroups <- function(pkgInstall) {
  set(
    pkgInstall, NULL, "installSafeGroups",
    as.integer(factor(paste0(
      paddedFloatToChar(pkgInstall$installSafeGroups,
                        padL = max(nchar(pkgInstall$installSafeGroups), na.rm = TRUE)
      ), "_",
      # !pkgInstall[["isBinaryInstall"]],
      !pkgInstall[["localFile"]] %in% useRepository
    )))
  )
  data.table::setorderv(pkgInstall, c("installSafeGroups", "Package")) # alphabetical order
}

getArchiveDetails <- function(pkgArchive, ava, verbose, repos) {
  cols <- c("PackageUrl", "dayAfterPutOnCRAN", "dayBeforeTakenOffCRAN", "repo",
            "VersionOnRepos", "availableVersionOK")
  commonCols <- intersect(cols, colnames(pkgArchive))

  numGroups <- NROW(unique(pkgArchive[["Package"]]))
  messageVerbose("Identifying date range when package versions appear in Archive for ",
                 numGroups, " package(s)",
                 verbose = verbose,
                 verboseLevel = 2
  )

  pkgArchive[, (cols) := {
    ret <- getArchiveDetailsInnerMemoise(Repository, ava, Package, cols, versionSpec, inequality, secondsInADay, .GRP,
                                         numGroups, verbose, repos, srcPackageURLOnCRAN)
  }, by = c("Package")]#, "Repository")] # multiple repositories may have same version and it is OK ... do loop inside

  # need to update binary or src because the getArchiveDetailsInner doesn't address this --
  #   it just returns the PackageURL
  bins <- isBinary(pkgArchive$PackageUrl, needRepoCheck = FALSE)
  bins <- isBinaryCRANRepo(pkgArchive$Repository) %in% TRUE | bins %in% TRUE
  needBinsUpdate <- !is.na(bins %in% TRUE)
  if (any(needBinsUpdate)) {
    set(pkgArchive, which(needBinsUpdate), "binOrSrc", binOrSrc(bins[needBinsUpdate]))
    pkgArchive <- updateReposForSrcPkgs(pkgArchive)
  }

  hasVoR <- which(!is.na(pkgArchive[["VersionOnRepos"]]))
  set(pkgArchive, hasVoR, "Version", pkgArchive[["VersionOnRepos"]][hasVoR])
  pkgArchive <- unique(pkgArchive, by = c("Package", "repo", "availableVersionOK"))

  pkgArchive
}

parsePackageFullname <- function(pkgDT, sorted = TRUE) {
  set(pkgDT, NULL, "versionSpec", extractVersionNumber(pkgDT[["packageFullName"]]))
  wh <- which(!is.na(pkgDT$versionSpec))
  if (length(wh)) {
    set(pkgDT, wh, "inequality", extractInequality(pkgDT[["packageFullName"]][wh]))
  } else {
    set(pkgDT, NULL, "inequality", NA_character_)
  }
  # pkgDT[!is.na(pkgDT$versionSpec), inequality := extractInequality(pkgDT[["packageFullName"]])]
  if (isTRUE(sorted))
    setorderv(pkgDT, c("Package", "versionSpec"), order = c(1L, -1L), na.last = TRUE)
  pkgDT
}

removeBasePkgs <- function(pkgDT) {
  pkgDT[!Package %in% .basePkgs]
}

renameLocalGitPkgDT <- function(pkgInstall) {
  whGitHub2 <- pkgInstall$repoLocation %in% .txtGitHub
  fns <- pkgInstall$localFile
  if (any(whGitHub2)) {
    pkgInstall[whGitHub2 %in% TRUE, localFile := {
      out <- basename(renameLocalGitTarWSHA(localFile, SHAonGH))
      keepOnlyBinary(out)
    }, by = seq(sum(whGitHub2))]
  }
}

renameLocalGitTarWSHA <- function(localFile, SHAonGH) {
  ##### Vectorized on localFile #####
  if (length(localFile)) {
    splitted <- lapply(basename(localFile), function(lf) strsplit(lf, "_")[[1]])
    newSHAname <- Map(spli = splitted, SHA = SHAonGH, function(spli, SHA) {
      paste0(spli[1], "-", SHA, "_", paste(spli[-1], collapse = "_"))
    })
    newSHAname <- unlist(file.path(dirname(localFile), newSHAname))
    fileRenameOrMove(localFile, newSHAname)
    out <- newSHAname
  } else {
    out <- ""
  }
  out
}

#' @importFrom stats na.omit
copyBuiltToCache <- function(pkgInstall, tmpdirs, copyOnly = FALSE) {
  if (!is.null(pkgInstall)) {
    if (nzchar(.requirePkgInfoDir())) {
      out <- try(Map(td = tmpdirs, function(td) {
        tdPkgs <- dir(td, full.names = TRUE,
                      pattern = paste0("\\.zip|\\.tar\\.gz|", macBinaryFileExtGrep))
        if (length(tdPkgs)) {
          pkgs <- Map(td = tdPkgs, function(td) strsplit(basename(td), split = "_")[[1]][1])
          pkgsInstalled <- pkgInstall[match(pkgs, Package)]
          isGitHub <- pkgsInstalled$repoLocation %in% .txtGitHub
          if (any(isGitHub)) {
            SHA <- pkgsInstalled$SHAonGH[isGitHub]
            tdPkgs[isGitHub] <- renameLocalGitTarWSHA(tdPkgs[isGitHub], SHA)
          }

          for (i in seq_along(tdPkgs)) {
            repo <- if (!is.null(pkgsInstalled$Repository)) pkgsInstalled$Repository[i] else NA_character_
            if (is.na(repo) || !nzchar(repo)) next
            destDir <- cachePkgDirForRepo(repo, create = TRUE)
            destFile <- file.path(destDir, basename(tdPkgs[[i]]))
            if (!file.exists(destFile)) {
              if (isTRUE(copyOnly)) {
                suppressWarnings(file.copy(tdPkgs[[i]], destFile))
              } else {
                suppressWarnings(fileRenameOrMove(tdPkgs[[i]], destFile))
              }
            }
          }
        }
      }))
      if (is(out, "try-error")) {
        browserDeveloper("Error 253; please contact developer")
      }
    }
  }
}

NoPkgsSupplied <- "No packages supplied"

anyHaveHEAD <- function(packages) {
  # Vectorize
  haveHead <- grepl(HEADgrepWithParentheses, packages)
  if (any(haveHead)) {
    origPackages <- packages
    packages[haveHead] <- trimVersionNumber(packages[haveHead])
    names(packages)[haveHead] <- origPackages[haveHead]
  }
  packages
}

HEADgrepWithParentheses <- " *\\(HEAD\\)"
hasHEADtxt <- "hasHEAD"

packageFullNameFromSnapshot <- function(snapshot) {
  isGH <- !is.na(snapshot$GithubRepo) & nzchar(snapshot$GithubRepo)
  out <- ifelse(isGH,
                paste0(
                  snapshot$GithubUsername, "/", snapshot$GithubRepo, "@",
                  snapshot$GithubSHA1
                ), snapshot[["Package"]]
  )
  ## Only append "(==Version)" for non-GitHub rows with a Version. A GH ref
  ## already has its identity locked by the SHA; appending "(==NA)" produces
  ## a malformed ref like "owner/repo@sha@NA" downstream and pak fails to
  ## resolve it.
  hasVersion <- !is.na(snapshot[["Version"]]) & nzchar(snapshot[["Version"]])
  appendVer <- hasVersion & !isGH
  out[appendVer] <- paste0(out[appendVer], " (==", snapshot[["Version"]][appendVer], ")")
  out <- addNamesToPackageFullName(out, snapshot[["Package"]])
  out
}

#' Update installed packages with latest available versions
#'
#' Similar to `update.packages`, but works for archived, non-archived,
#' and Github packages.
#'
#' @param libPaths The library to update; defaults to `.libPaths()[1]`
#' @param purge Logical. Should the assessment of `installed.packages` purge the cached
#'   version. Default is `FALSE`
#' @inheritParams Require
#' @return Run for its side effect, namely, updating installed packages to their latest
#' possible state, whether they are on CRAN currently, archived, or on GitHub.
#' @export
#'
updatePackages <- function(libPaths = .libPaths()[1], purge = FALSE,
                           verbose = getOption("Require.verbose")) {

  libPaths <- doLibPaths(libPaths, standAlone = FALSE)

  ip <- doInstalledPackages(libPaths, purge, includeBase = FALSE)

  ref <- ip$GithubRef
  ineq <- "HEAD"
  head <- paste0(" (", ineq, ")")
  gsf <- !is.na(ip$GithubSubFolder) | nzchar(ip$GithubSubFolder) | ip$GithubSubFolder != "NA"
  pkgs <- paste0(ifelse(
    !is.na(ip$GithubRepo),
    paste0(ip$GithubUsername, "/", ip$GithubRepo,
           ifelse(gsf, "", paste0("/", ip$GithubSubFolder)),
           "@", ref, head),
    # github
    paste0(ip[["Package"]], head) # cran
  ))

  # check for SHAs that need user input to update:
  pkgsSplit <- strsplit(pkgs, split = "@")
  hasHash <- mapply(pkg = pkgsSplit, function(pkg) {
    out <- FALSE
    if (length(pkg) > 1) {
      br <- strsplit(pkg[-1], split = " *\\(")[[1]][1]
      if (grepl("[[:alnum:]]{40}", br)) out <- TRUE
    }
    out
  })
  if (any(hasHash)) {
    message("These packages are installed with an exact hash from github; \n",
            "you want these updated? (if Y, then will take `main` branch; ",
            "if N, then no updates for those packages)")
    message(paste(pkgs[hasHash], collapse = "\n"))
    if (interactive()) {
      updated <- readline("Y or N: ")
    } else {
      updated <- "N"
    }
    updated <- tolower(updated)
    if (startsWith(updated, "y"))
      pkgs[hasHash] <- paste0(unlist(lapply(pkgsSplit[hasHash], function(x) x[[1]])), " (HEAD)")
    else {
      message("Not updating these packages")
      pkgs <- pkgs[!hasHash]
    }
  }

  Install(pkgs, purge = purge, verbose = verbose)
}

getVersionOnReposLocal <- function(pkgDT) {
  if (nzchar(.requirePkgInfoDir())) {
    set(pkgDT, NULL, "tmpOrder", seq(NROW(pkgDT)))
    if (any(is.na(pkgDT[["VersionOnRepos"]]))) {
      pkgDTList <- split(pkgDT, !is.na(pkgDT[["VersionOnRepos"]]))
      if (!is.null(pkgDTList$`FALSE`)) {
        localFilesOuter <- dir(.requirePkgInfoDir())
        pkgNoVoR <- pkgDTList$`FALSE`
        wh <- pkgNoVoR[["repoLocation"]] %in% .txtGitHub
        if (any(wh)) {
          pkgNoVoR[which(wh %in% TRUE), localFile := {
            PackagePattern <- paste0("^", Package, ".*(\\_|\\-)+.*", Branch)
            localFiles <- grep(PackagePattern, localFilesOuter, value = TRUE) # can be length 0, 1, or >1 e.g., tar.gz, zip
            if (length(localFiles) == 0) {
              localFiles <- ""
            } else {
              isBinaryFile <- isBinary(localFiles)
              if (any(isBinaryFile %in% TRUE)) {
                localFiles <- localFiles[isBinaryFile]
              }
              if (length(localFiles) > 1) localFiles <- localFiles[1]
            }
            localFiles
          },
          by = seq(sum(wh))
          ]
          hasLocalNow <- (nchar(pkgNoVoR$localFile) > 0) %in% TRUE
          if (any(hasLocalNow %in% TRUE)) {
            pkgNoVoR[hasLocalNow, VersionOnRepos := extractVersionNumber(filenames = pkgNoVoR$localFile)]
            pkgNoVoR[hasLocalNow, haveLocal := .txtLocal]
          }
          pkgDT <- rbindlistRecursive(pkgDTList) # the pkgNoVoR was just a pointer
          setorderv(pkgDT, "tmpOrder")
          set(pkgDT, NULL, "tmpOrder", NULL)
        }
      }
    }
  }

  pkgDT
}

browserDeveloper <- function(mess = "", envir = parent.frame()) {
  if (identical(SysInfo[["user"]], "emcintir")) {
    print(mess)
  } else {
    stop(mess)
  }
}

updateReposForSrcPkgs <- function(pkgInstall, verbose = getOption("Require.verbose")) {

  if (isLinux()) {
    isBinary <- isBinaryCRANRepo(pkgInstall$Repository)
    dontInstallBecauseForceSrc <- pkgInstall[["Package"]] %in% sourcePkgs()
    needSrc <- isBinary & dontInstallBecauseForceSrc
    if ("lme4" %in% pkgInstall[["Package"]]) {
      if (packageVersion("Matrix") < "1.6.5") {
        needSrc[pkgInstall[["Package"]] %in% "lme4"] <- TRUE
        messageVerbose("lme4 is being installed. Matrix version is <1.6.5, which means that lme4 must ",
                       "be installed from source. Proceeding with installing lme4 from source. Alternatively, ",
                       "cancel this, Install('Matrix (>=1.6.5)' and try again.", verbose = verbose)
      }
    }

    # }
    #
    # if (!isWindows() && !isMacOS() &&
    #     any(pkgInstall$isBinaryInstall & pkgInstall$localFile %in% useRepository)) {
    #   dontInstallBecauseForceSrc <- pkgInstall[["Package"]] %in% sourcePkgs()
    #   mayNeedSwitchToSrc <- pkgInstall$localFile %in% useRepository & dontInstallBecauseForceSrc
    #   pkgInstall[
    #     which(mayNeedSwitchToSrc),
    #     isBinaryInstall := isWindows() | isMacOS()
    #   ]
    #   needSwitchToSrc <- mayNeedSwitchToSrc & pkgInstall$isBinaryInstall %in% FALSE
    #   if (any(needSwitchToSrc %in% TRUE)) {
    # Eliot commented this March 28, 2024 -- posit Package Manager can handle this now
    # if (any(isBinaryCRANRepo(getOption("repos")[1]))) {
    #   nams <- pkgInstall[needSwitchToSrc][["Package"]]
    #   warning(
    #     "The CRAN repository is a binary repository. However, ",
    #     paste(nams, collapse = comma), isAre(nams), " identified in `sourcePkgs()`, ",
    #     " indicating installation from source; if these source installs fail, try changing ",
    #     "to set \noptions(Require.otherPkgs = c('",
    #     paste(setdiff(getOption("Require.otherPkgs"), nams), collapse = "', '"), "'))",
    #     "\nremoving ", paste(nams, collapse = comma)
    #   )
    # }
    if (all(isBinaryCRANRepo(getOption("repos")))) {
      warning(
        paste(pkgInstall[needSwitchToSrc][["Package"]], collapse = comma), " is identified in `sourcePkgs()`, ",
        "indicating it should normally be installed from source; however, there is no source CRAN repository.",
        "Please add one to the `options(repos)`, e.g., with ",
        "options(repos = c(getOption('repos'), CRAN = 'https://cloud.r-project.org')).",
        "Proceeding with the binary repository, which may not work"
      )
    } else {
      nonBinaryRepos <- getOption("repos")[!isBinaryCRANRepo(getOption("repos"))]
      if (length(nonBinaryRepos) == 1) {
        pkgInstall[which(needSrc), `:=`(Repository = contrib.url(nonBinaryRepos),
                                        repo = nonBinaryRepos,
                                        binOrSrc = binOrSrc(FALSE))]
        # pkgInstall[which(needSrc), Repository := nonBinaryRepos]

        # pkgInstall[whArchive %in% TRUE & needSwitchToSrc, Repository := getArchiveURL(nonBinaryRepos, Package)]

        # if there are multiple non-binary repos
      } else { # (length(nonBinaryRepos) > 1) {
        packageExists <- FALSE
        if (is.null(pkgInstall[["isBinaryInstall"]])) {
          set(pkgInstall, NULL, "isBinaryInstall", isBinaryCRANRepo(curCRANRepo = pkgInstall$Repository))
        }

        set(pkgInstall, NULL, "origOrd", seq_len(NROW(pkgInstall)))
        setorderv(pkgInstall, cols = c("isBinaryInstall"))

        for (ind in seq(nonBinaryRepos)) {
          nbrContrib <- contrib.url(nonBinaryRepos)
          whArchive <- pkgInstall$installFrom %in% .txtArchive
          dontInstallBecauseForceSrc <- pkgInstall[["Package"]] %in% sourcePkgs()
          if (!is.null(pkgInstall$localFile)) {
            mayNeedSwitchToSrc <- pkgInstall$localFile %in% useRepository & dontInstallBecauseForceSrc
            needSwitchToSrc <- mayNeedSwitchToSrc & pkgInstall$isBinaryInstall %in% FALSE
            pkgInstallNeededHere <- pkgInstall[!whArchive %in% TRUE & needSwitchToSrc]
            apTmp <- available.packages(contriburl = nbrContrib[ind])
            packageExists <- pkgInstallNeededHere[["Package"]] %in% apTmp[, "Package"]
            if (any(packageExists)) {
              pkgInstall[Package %in% pkgInstallNeededHere[["Package"]][packageExists],
                         `:=`(Repository, nbrContrib[ind])]
              needSwitchToSrc <- mayNeedSwitchToSrc & pkgInstall$isBinaryInstall %in% FALSE
            }
            if (isTRUE(all(packageExists)))
              break
          } else {
            set(pkgInstall, NULL, "keep", seq_len(NROW(pkgInstall)))
            pkgInstall[which(dontInstallBecauseForceSrc), keep := .I[1], by = "Package"]
            pkgInstall <- pkgInstall[unique(pkgInstall$keep)]
            set(pkgInstall, NULL, "keep", NULL)
            setorderv(pkgInstall, "origOrd")
          }
          # needSwitchToSrc <- mayNeedSwitchToSrc & pkgInstall$isBinaryInstall %in% FALSE


        }
        # } else {
        #   pkgInstall[!whArchive %in% TRUE & needSwitchToSrc, Repository := contrib.url(nonBinaryRepos)]
      }

      #   }
    }
  }
  pkgInstall
}

messagesAboutWarnings <- function(w, toInstall, returnDetails, tmpdir, verbose = getOption("Require.verbose")) {
  # This is a key error; cached copy is corrupt; this will intercept, delete it and reinstall all right here

  pkgName <- extractPkgNameFromWarning(w$message)
  outcome <- FALSE
  needWarning <- TRUE
  if (identical(pkgName, w$message)) { # didn't work
    pkgName <- gsub(".+\u2018(.+)\u2019.*", "\\1", w$message)
  }
  if (identical(pkgName, w$message)) { # didn't work again
    if (any(grepl("cannot open URL", pkgName))) { # means needs purge b/c package is on CRAN, but not that url
      url <- gsub(".+(https://.+\\.zip).+", "\\1", pkgName)
      url <- gsub(".+(https://.+\\.tar\\.gz).+", "\\1", url)
      url <- gsub(paste0(".+(https://.+", macBinaryFileExtGrep, ").+"), "\\1", url)
      pkgName <- extractPkgName(filenames = basename(url))

      repos <- unique(toInstall[["repos"]])
      try(lapply(repos, function(repo) purgeAvailablePackages(repo, purge = TRUE)))
      # try(purgeAvailablePackages(repos = repos, purge = TRUE))
      # try(dealWithCache(purge = TRUE, checkAge = FALSE))
      messageVerbose("purging availablePackages; trying to download ", pkgName, " again",
                     verbose = verbose)
      res <- try(Install(pkgName))
      if (is(res, "try-error")) {
        needWarning <- TRUE
      } else {
        outcome2 <- attr(res, "Require")
        if (identical(.txtNoneAvailable, outcome2$installResult)) {
          needWarning <- TRUE
        } else {
          needWarning <- FALSE
          outcome <- TRUE
          rowsInPkgDT <- grep(pkgName, toInstall[["Package"]])
          outcome2JustOne <- outcome2[outcome2[["Package"]] %in% toInstall[["Package"]]]
          if (!is.null(outcome2JustOne$installed))
            toInstall[rowsInPkgDT, installed := outcome2JustOne$installed]
          if (!is.null(outcome2JustOne$installResult))
            toInstall[rowsInPkgDT, installResult := outcome2JustOne$installResult]
        }
      }
    }

  }

  rowsInPkgDT <- which(toInstall[["Package"]] %in% pkgName)
  if (length(rowsInPkgDT) && any(toInstall[rowsInPkgDT]$installed %in% FALSE)) {
    toInstall[rowsInPkgDT, installed := outcome]
    toInstall <- appendInstallResult(toInstall, rowsInPkgDT, w$message, sep = "; ")
    # toInstall[rowsInPkgDT, installResult := w$message]

    if (any(grepl("cannot remove prior installation of package", w$message))) {
      warning("Is ", pkgName, " loaded in another R session? Please close all sessions before installing packages")
      needWarning <- FALSE
    } else {
      needWarning <- TRUE
    }
  }

  inUse <- "in use and will not be installed"
  isInUse <- grepl(inUse, w$message)
  if (isInUse) {
    toInstall[Package %in% pkgName, installResult := inUse]
  }
  if (length(rowsInPkgDT) && any(isInUse)) {
    if (!all(toInstall$installed[rowsInPkgDT] %in% FALSE) &&
        !all(toInstall$installResult[rowsInPkgDT] %in% w$message)) {
      set(toInstall, rowsInPkgDT, "installed", FALSE)
      # toInstall[rowsInPkgDT, installed := FALSE]
      set(toInstall, rowsInPkgDT, "installResult", w$message)
    }
    messageVerbose(w$message, verbose = verbose)
    if (length(grep("Require", pkgName)))
      needWarning <- FALSE
  }

  # Case where the local file may be corrupt
  needReInstall <- FALSE
  failedMsg <- grepl(
    paste(
      .txtInstallationNonZeroExit,
      .txtInstallationPkgFailed,
      .txtCannotOpenFile, sep = "|"),
    w$message)
  if (any(failedMsg)) {
    if (any(grepl(.txtCannotOpenFile, w$message))) {
      logFile <- dir(tmpdir, full.names = TRUE, pattern = "\\.log")
      fe <- file.exists(logFile)
      if (isTRUE(any(fe))) {
        logFile <- head(logFile[order(file.info(logFile)[, "mtime"], decreasing = TRUE)], 1)
        log <- readLines(logFile)
        if (any(grepl("problem copying.*permission denied", log, ignore.case = TRUE))) {
          unlink(logFile)
          # warning is already done in sysDo
          stop("Is some other process using ", pkgName, "?", "\nPerhaps close other R sessions?", call. = FALSE)
        }
      }
    }

    # Don't delete here yet... it may need to retry
    # unlink(toInstall[Package %in% pkgName]$localFile)
    # unlink(dir(tmpdir, pattern = paste0("^", pkgName, "_[[:digit:]]", collapse = "|"), full.names = TRUE))
    if (isTRUE(any(grepl(.txtCannotOpenFile, w$message)))) {
      stop(.txtPkgFailed, pkgName, .txtRetrying)
    }
  }

  if (nzchar(.requirePkgInfoDir())) {
    if (isTRUE(unlist(grepV(pkgName, .requirePkgInfoDir()))) || needReInstall) {
      messageVerbose(verbose = verbose, verboseLevel = 1, "Cached copy of ", basename(pkgName), " was corrupt; deleting; retrying")
      # unlink(dir(.requirePkgInfoDir(), pattern = basename(pkgName), full.names = TRUE)) # delete the erroneous Cache item
      retrying <- try(Require(toInstall[Package %in% basename(pkgName)][["packageFullName"]],
                              require = FALSE,
                              verbose = verbose, returnDetails = returnDetails,
                              dependencies = FALSE)) # dependencies = FALSE b/c it should already have what it needs
      if (is(retrying, "try-error")) {
        needWarning <- TRUE
      } else {
        needWarning <- FALSE
      }
    }
  } else {
    needWarning <- TRUE
  }

  if (isTRUE(needWarning)) {
    warning(w)
  }
}

getArchiveURL <- function(repo, pkg, type = getOption("pkgType")) {
  file.path(contrib.url(repo, type), .txtArchive, pkg)
}

isGitHub <- function(pkg, filenames) {
  if (!missing(filenames)) {
    isGH <- grepl("-[[:alnum:]]{40}_", basename(filenames))
  } else {
    isGH <- extractPkgGitHub(pkg)
    isGH <- !is.na(isGH)
  }
  isGH
}

#' @rdname Require
#' @export
#' @details
#' `Install` is the same as `Require(..., require = FALSE)`, for convenience.
Install <- function(packages, packageVersionFile,
                    libPaths, # nolint
                    install_githubArgs = list(),
                    install.packagesArgs = list(INSTALL_opts = "--no-multiarch"),
                    standAlone = getOption("Require.standAlone", FALSE),
                    install = TRUE,
                    repos = getOption("repos"),
                    purge = getOption("Require.purge", FALSE),
                    verbose = getOption("Require.verbose", FALSE),
                    type = getOption("pkgType"),
                    typeExplicit = !missing(type),
                    upgrade = FALSE,
                    ...) {

  if (!missing(packages)) {
    packagesSubstituted <- substitute(packages)
    packages <- substitutePackages(packagesSubstituted, parent.frame())
  }

  Require(packages,
          packageVersionFile,
          libPaths,
          install_githubArgs,
          install.packagesArgs,
          standAlone,
          install = install,
          require = FALSE,
          repos,
          purge,
          verbose,
          type,
          ## named, deliberately: `type` above is positional, so missing(type)
          ## inside Require() would always be FALSE when called from here and
          ## every Install() would look like an explicit type request
          typeExplicit = typeExplicit,
          upgrade,
          ...
  )
}

rmPackageFirst <- function(toInstall, libPaths, verbose) {
  cantUpdateDeps <- toInstall[["Package"]] %in% c("Require", "data.table")
  if (any(cantUpdateDeps))
    toInstall <- toInstall[-which(cantUpdateDeps)]
  cantUpdateLoaded <- toInstall[["Package"]] %in% loadedNamespaces()
  if (any(cantUpdateLoaded)) {
    toRm <- toInstall[["Package"]][!cantUpdateLoaded]
    ip <- .installed.pkgs(libPaths)
    toRm2 <- toRm[toRm %in% unname(ip[, "Package"])]
    toInstall <- toInstall[which(!cantUpdateLoaded)]
    if (length(toRm2))
      try(remove.packages(toRm2), silent = TRUE)
    messageVerbose("removed all the packages that already were installed prior to new install", verbose = verbose)
  }
  toInstall
}

downloadAndBuildToLocalFile <- function(Account, Repo, Branch, Package, GitSubFolder, verbose, VersionOnRepos) {
  gitRepo <- paste0(Account, "/", Repo, "@", Branch)
  names(gitRepo) <- Package
  out <- downloadRepo(gitRepo, subFolder = GitSubFolder,
                      overwrite = TRUE, destDir = ".", verbose = verbose)
  out1 <- try(build(Package, verbose = verbose, quiet = verbose <= 0 || verbose >= 5,
                    VersionOnRepos = VersionOnRepos))
  fn <- dir(pattern = paste0("^", Package, "_.+tar.gz"))
  normPath(fn)
}


needRebuildAndInstall <- function(needRebuild, pkgInstall, libPaths, install.packagesArgs,
                                  repos, purge, startTime, type,
                                  pkgInstallTmp, tmpdir, verbose = getOption("Require.verbose")) {
  if (any(needRebuild)) {
    messageVerbose("Trying to rebuild and install GitHub build fails... ", verbose = verbose)
    pkgInstall[which(needRebuild), needRebuild := repoLocation]
    pkgInstallList <- split(pkgInstall, by = "needRebuild")
    # names(pkgInstallList) <- c("No", .txtGitHub)
    pkgInstallList <- downloadGitHub(pkgInstallList, libPaths, tmpdir = tmpdir, verbose, install.packagesArgs)
    maxGroup <- 1
    numPackages <- NROW(pkgInstallList[[.txtGitHub]])
    pkgInstallList[[.txtGitHub]][, installOrder := seq(.N)] # renumber the installOrder
    pkgInstallList <- Map(
      toInstall = pkgInstallList[.txtGitHub],
      MoreArgs = list(
        repos = repos, purge = purge,
        install.packagesArgs = install.packagesArgs, numPackages = numPackages,
        numGroups = maxGroup, startTime = startTime, type = type,
        libPaths = libPaths,
        tmpdir = tmpdir, verbose = verbose
      ),
      installAll
    )
    pkgInstall <- rbindlist(list(pkgInstallTmp[!Package %in% pkgInstallList[[.txtGitHub]][["Package"]]],
                                 pkgInstallList[[.txtGitHub]]), use.names = TRUE, fill = TRUE)
  }
  pkgInstall
}

## Expand any character element containing newlines into one element per line,
## trimming whitespace and dropping blank or `#`-commented lines. Lets users
## paste a heredoc-style block into `Require()`, e.g.
##   Require("
##     dplyr
##     lme4
##     # ggplot2
##     PredictiveEcology/LandR@development
##   ")
## Single-line strings and named vectors (which can't contain newlines in
## practice) pass through untouched. If a named element somehow expands to
## more than one package the name is dropped, since one alias cannot map to
## several packages.
parseMultiLinePackages <- function(packages) {
  if (!is.character(packages) || !any(grepl("\n", packages, fixed = TRUE))) {
    return(packages)
  }
  hadNames <- !is.null(names(packages))
  nms <- if (hadNames) names(packages) else rep("", length(packages))
  parts <- Map(function(p, nm) {
    if (!grepl("\n", p, fixed = TRUE)) {
      out <- p
      names(out) <- nm
      return(out)
    }
    lines <- trimws(strsplit(p, "\n", fixed = TRUE)[[1]])
    lines <- lines[nzchar(lines) & !grepl("^#", lines)]
    if (!length(lines)) return(character(0))
    names(lines) <- if (nzchar(nm) && length(lines) == 1L) nm else rep("", length(lines))
    lines
  }, packages, nms)
  ## Strip Map's outer list names (which default to the input *values* when
  ## the input vector had no names) so unlist doesn't prefix them onto the
  ## inner names we set above.
  names(parts) <- NULL
  out <- unlist(parts, use.names = TRUE)
  if (is.null(out)) return(character(0))
  if (!hadNames || !any(nzchar(names(out)))) names(out) <- NULL
  out
}

substitutePackages <- function(packagesSubstituted, envir = parent.frame()) {

  ## `Require({ dplyr; lme4; PredictiveEcology/LandR@development })`:
  ## R's parser accepts a `{...}` block in place of a vector, so each line is
  ## a standalone expression we can deparse back into a package spec. This
  ## avoids the parse errors that would arise from passing bare unquoted
  ## names as positional args. Comments (`# ...`) are stripped by the parser
  ## before we ever see the tree, so they don't appear in the result.
  ## Version constraints like `pkg (>= 1.0)` still don't parse inside `{...}`
  ## and remain a parse error -- use the quoted/multi-line-string form for
  ## those.
  if (is.call(packagesSubstituted) &&
      identical(packagesSubstituted[[1L]], as.name("{"))) {
    exprs <- as.list(packagesSubstituted)[-1L]
    return(vapply(exprs, function(e) paste(deparse(e), collapse = ""),
                  character(1L)))
  }

  # Deal with non character strings first
  packages2 <- if (isTRUE(all(is.character(packagesSubstituted)))) {
    packagesSubstituted
  } else {
    deparse(packagesSubstituted)
  }
  isName <- is.name(packagesSubstituted)
  # isName <- is.name(packages2)
  if (isName) {
    packagesTmp2 <- get0(packages2, envir = envir)
    if (is(packagesTmp2, "list")) {
      kk <- lapply(packagesTmp2, substitutePackages, envir = envir)
    }
    if (is.character(packagesTmp2))
      packages <- packagesTmp2

    # packages <- packages2
  } else {
    isGH <- isGitHub(packages2)
    if (isTRUE(isGH)) {
      packages <- packages2
    } else {
      # if (length(packages2) > 1) {
      #   packagesTmp2 <- packages2
      # } else {
      #   # packagesTmp2 <- get0(packages2, envir = envir)
      #   # if (is.character(packagesTmp2))
      #   #   packages <- packagesTmp2
      # }
      # }

      # Recursion internally to c or list
      if (is.call(packagesSubstituted) && !is.character(packagesSubstituted) || is(packagesSubstituted, "list")) {
        packages <- try(eval(packagesSubstituted, envir = envir), silent = TRUE)
        if (is(packages, "try-error") || is(packagesSubstituted, "list")) {
          nams <- names(packagesSubstituted[-1])
          if (is.null(nams)) nams <- rep("", length(packagesSubstituted[-1]))
          packages <- unlist(Map(i = seq_len(length(packagesSubstituted[-1])) + 1,
                                 nam = nams, function(i, nam) {
                                   # First try to evaluate; only if fails do we do the recursion on each element
                                   out <- try(eval(packagesSubstituted[i], envir = envir), silent = TRUE) # e.g., paste("SpaDES (>= ",version,")")
                                   if (is(out, "try-error"))
                                     out <- substitutePackages(packagesSubstituted = as.list(packagesSubstituted)[[i]], envir = envir)
                                   if (!is.null(nam))
                                     if (nzchar(nam))
                                       names(out) <- nam
                                   out
                                 }))
        }
      }
    }
  }

  if (!exists("packages", inherits = FALSE))
    packages <- packages2

  packages
}

clonePackages <- function(rcf, ipa, libPaths, verbose = getOption("Require.verbose")) {
  mess <- capture.output(
    type = "message",
    ip <- installed.packages(lib.loc = rcf, fields = c("Built", "NeedsCompilation"))
  )
  clearErrorReadRDSFile(mess, rcf)
  #
  # oo <- capture.output(type = "message",
  #                      ip <- installed.packages(lib.loc = rcf, fields = c("Built", "NeedsCompilation")))
  #                      # ip <- .installed.pkgs(lib.loc = rcf, which = c("Built", "NeedsCompilation")))
  ignorePackages <- character()
  ipCanTryNeedsNoCompilAndGoodRVer <- canClone(ip)
  alreadyInstalledCanClone <- intersect(rownames(ipCanTryNeedsNoCompilAndGoodRVer), ipa$pkgs)
  alreadyInstalledCanClone <- ipCanTryNeedsNoCompilAndGoodRVer[, "Version"][alreadyInstalledCanClone]
  alreadyInstalledCanClone <- alreadyInstalledCanClone[!names(alreadyInstalledCanClone) %in% sourcePkgs()]
  # Never clone pak / callr / processx / cli -- they bundle native binaries
  # and helper executables that don't survive a file-by-file copy on Windows
  # (see .pakNoCopyPkgs() in R/pak.R for why). Keeping them in the install
  # plan (cantClone) forces a fresh install via the normal install machinery.
  alreadyInstalledCanClone <- alreadyInstalledCanClone[!names(alreadyInstalledCanClone) %in% .pakNoCopyPkgs()]

  if (length(grep("Error in readRDS", mess))) {
    # This means that the packages in rcf are broken
    aa <- packageHasError(rcf)
    igns <- unlist(unname(aa))
    if (length(igns))
      ignorePackages <- names(alreadyInstalledCanClone)[names(alreadyInstalledCanClone) %in% igns]
  }

  if (length(ignorePackages))
    alreadyInstalledCanClone <- alreadyInstalledCanClone[!names(alreadyInstalledCanClone) %in% ignorePackages]
  wantToInstall <- ipa$available[, "Version"]
  cantClone <- setdiffNamed(wantToInstall, alreadyInstalledCanClone)# if (isWindows()) {
  canClone <- setdiffNamed(wantToInstall, cantClone)
  canClone <- names(canClone)
  if (length(canClone)) {
    mess <- paste0("  -- Cloning (", length(canClone)," of ", length(wantToInstall),
                   ") instead of Installing (don't need compiling): ",
                   paste(canClone, collapse = comma))
    mess <- paste0WithLineFeed(mess)
    messageVerbose(green(mess), verbose = verbose)

    linkOrCopyPackageFiles(Packages = canClone, fromLib = rcf[1], toLib = libPaths[1], ip = ip)
    ipa$pkgs <- names(cantClone)
    messageVerbose(green("... Done!"), verbose = verbose)
    ipa$available <- ipa$available[ipa$pkgs, , drop = FALSE]
  }
  ipa
}

linkOrCopyPackageFiles <- function(Packages, fromLib, toLib, ip) {
  if (missing(ip)) {
    mess <- capture.output(
      type = "message",
      ip <- installed.packages(fromLib, fields = c("Built", "NeedsCompilation"))
    )
    clearErrorReadRDSFile(mess, fromLib)
  }
  cant <- cantClone(ip)
  # Defense-in-depth: even if a caller bypasses clonePackages() and reaches
  # this helper directly, pak/callr/processx/cli must never be file-copied
  # across libs (their embedded native binaries don't survive on Windows).
  cant <- unique(c(sourcePkgs(), cant[, "Package"], .pakNoCopyPkgs()))
  Packages <- setdiff(Packages, cant)
  linkOrCopyPackageFilesInner(Packages, fromLib, toLib)
  return(invisible())
}

linkOrCopyPackageFilesInner <- function(Packages, fromLib, toLib) {
  ret <- lapply(Packages, function(packToClone) {
    from <- dir(dir(fromLib, pattern = paste0("^", packToClone, "$"), full.names = TRUE),
                recursive = TRUE, all.files = TRUE)
    fromFull <- file.path(fromLib, packToClone, from)
    to <- file.path(toLib, packToClone, from)
    dups <- fromFull %in% to
    if (any(dups)) {
      fromFull <- fromFull[!dups]
      to <- to[!dups]
    }
    if (length(fromFull) > 0) {
      dirs <- unique(dirname(to))
      checkPath(dirs[order(nchar(dirs), decreasing = TRUE)], create = TRUE)
      outs <- linkOrCopy(fromFull, to, allowSymlink = FALSE)
    }
  })
}


getArchiveDetailsInner <- function(Repository, ava, Package, cols, versionSpec, inequality,
                                   secondsInADay, .GRP,
                                   numGroups, verbose, repos, srcPackageURLOnCRAN) {
  if (any(is.na(Repository)) || is.null(Repository)) {
    Repository <- unique(ava[[Package]]$repo) # this can make it longer than other items (Package, versionSpec, inequality)
    Repository <- Repository[match(repos, Repository)]
    if (length(Repository) > length(Package)) {
      len <- length(Repository)
      # Package <- rep(Package, len)
      versionSpec <- rep(versionSpec, len)
      inequality <- rep(inequality, len)
    }
  }
  ret <- mapply(x = cols, USE.NAMES = TRUE, function(x) NA_character_, SIMPLIFY = FALSE)
  ret <- as.data.table(ret)
  set(ret, NULL, "availableVersionOK", FALSE)

  for (ind in seq(Repository)) {
    repositoryHasArchives <- if (!is.na(Repository[ind]) && !is.null(ava[[Package]]$repo)) {
      unlist(lapply(ava[[Package]]$repo, grepl, x = Repository[ind]))
    } else {
      FALSE
    }
    if (all(!repositoryHasArchives) && length(repos) > length(unique(Repository[ind]))) {
      repositoryHasArchives <- unlist(lapply(ava[[Package]]$repo, grepl, x = repos))
    }

    if (any(repositoryHasArchives)) {
      Version2 <- extractVersionNumber(filenames = ava[[Package]]$PackageUrl)
      versionSpec2 <- unique(versionSpec[ind])
      vs2IsCharNA <- versionSpec2 %in% "NA"
      if (any(vs2IsCharNA))
        versionSpec2[vs2IsCharNA] <- NA
      if (length(Version2) > 0) {
        if (is.na(versionSpec2)) { # Version2 is length 0 when the package has nothing available
          nver <- numeric_version(Version2)
          highest <- max(nver)
          correctVersions <- which(highest == nver)
          if (length(correctVersions) > 1) {
            correctVersions <- correctVersions[ava[[Package]][correctVersions]$repo %in% Repository[ind]]
          }
        } else {
          if (identical(versionSpec2, "HEAD")) {
            correctVersions <- rep(FALSE, length(Version2))
            correctVersions[length(correctVersions)] <- TRUE
            names(correctVersions) <- Version2
          } else {
            # creates a named logical, where name is version2
            correctVersions <- compareVersion2(Version2, versionSpec2, inequality[ind])
          }

          if (all(correctVersions %in% FALSE)) {
            # This is case where the inequality cannot be fulfilled... check why:
            # e.g., NLMR == 1.1.1 ... did not exist on CRAN archives
            # case 1 -- package exists in archive, but not specific version
            # no version on CRAN
            if (length(correctVersions)) {
              ineq <- ">="
              altVersion <- compareVersion2(Version2, versionSpec2, ineq)
              if (!any(altVersion)) {
                ineq <- "<="
                altVersion <- compareVersion2(Version2, versionSpec2, ineq)
              }
              correctVersions <- NA
              if (any(altVersion)) {
                messageVerbose("Package ",
                               Package, " (", inequality[ind], "", versionSpec2,
                               ") is not available in `repos` supplied ",
                               "The recent available versions are:", verbose = verbose
                )
                messageDF(tail(n = 10, data.frame(Version = Version2,
                                                  ava[[Package]][, c("repo", "PackageUrl", "mtime")])), verbose = verbose)
                warnTxt <- msgPleaseChangeRqdVersion(Package, ineq, newVersion = tail(Version2[altVersion], 1))
                warning(warnTxt, call. = FALSE)

                break
              }
              # correctVersions <- altVersion
              # }

            }

          } else {
            latestCorrect <- try(tail(which(correctVersions), 1))
            if (is(latestCorrect, "try-error")) {
              browserDeveloper("Error 111; please contact developer")
            }
            correctVersions <- unique(c(latestCorrect, min(latestCorrect + 1, length(correctVersions))))
          }
        }
      } else {
        correctVersions <- NA
      }
      if (any(!is.na(correctVersions))) { # nothing on Archive that will fulfill the Version requirements
        if (length(correctVersions) == 1) correctVersions <- c(correctVersions, NA_integer_)
        earlyDate <- ava[[Package]][correctVersions[1]][["mtime"]] + secondsInADay
        ret <- ava[[Package]][correctVersions[1]][, c("PackageUrl", "mtime", "repo")]

        # if (isWindows() || isMacOS()) { # relevant for RSPM
        messageVerbose(.GRP, " of ", numGroups, ": ", Package,
                       verbose = verbose,
                       verboseLevel = 2
        )
        if (is.na(correctVersions[2])) { # if 2nd one is NA, it means it an archived package
          tf <- file.path(cachePkgDirForRepo(ret$repo), basename(ret$PackageUrl))
          fe <- file.exists(tf)
          if (fe) {
            dayBeforeTakenOffCRAN <- ava[[Package]][correctVersions[2]][["mtime"]]
          } else {
            dayBeforeTakenOffCRAN <- archivedOn(Package, pkgRelPath = ret$PackageUrl, verbose, repos,
                                                numGroups = numGroups,
                                                counter = .GRP,
                                                srcPackageURLOnCRAN, ret$repo
            )
            dayBeforeTakenOffCRAN <- dayBeforeTakenOffCRAN[[1]]$archivedOn
          }
        } else {
          dayBeforeTakenOffCRAN <- ava[[Package]][correctVersions[2]][["mtime"]]
        }

        set(ret, NULL, "dayBeforeTakenOffCRAN", dayBeforeTakenOffCRAN)
        # } else {
        #  set(ret, NULL, "dayBeforeTakenOffCRAN", NA_character_)
        # }
        setnames(ret, "mtime", "dayAfterPutOnCRAN")
        set(ret, NULL, "dayAfterPutOnCRAN", as.character(as.Date(ret$dayAfterPutOnCRAN)))
        set(ret, NULL, "dayBeforeTakenOffCRAN", as.character(as.Date(ret$dayBeforeTakenOffCRAN)))

        set(ret, NULL, "VersionOnRepos", Version2[correctVersions[1]])
        if (!is.na(correctVersions)[1]) {
          set(ret, NULL, "availableVersionOK", TRUE)
        }
        if (isTRUE(ret$availableVersionOK)) break
      } else {
        repoToAdd <- ava[[Package]][1][, c("repo")]
        if (!is.null(repoToAdd))
          ret[, repo := repoToAdd]
      }
    }
  }
  data.table::setcolorder(ret, cols)
  ret
}


# If a list of dependencies includes Remotes from DESCRIPTION file
# mergeRemotes <- function(depsList) {
#   lapply(depsList, function(dep) {
#     pkgDT <- parseGitHub(dep)
#     isGH <- !is.na(pkgDT$githubPkgName)
#     ver <- extractVersionNumber(pkgDT[["packageFullName"]])
#     verNotGH <- ver[!isGH]
#     hasVerNotGH <- !is.na(verNotGH)
#     verGH <- ver[isGH]
#     wh1 <- which(!isGH)[hasVerNotGH]
#     ma1 <- match(pkgDT[["Package"]][!isGH], pkgDT[["Package"]][isGH]) |> na.omit()
#     ma2 <- match(pkgDT[["Package"]][isGH], pkgDT[["Package"]][!isGH]) |> na.omit()
#     wh <- which(!is.na(ma2))
#     hasVer2 <- hasVerNotGH[ma2]#[wh]
#     ineq <- extractInequality(pkgDT[["packageFullName"]][ma2][hasVer2])#[!isGH])
#     # ma[!is.na(ver)]
#     newPFN <- paste0(pkgDT[["packageFullName"]][isGH][ma1][hasVer2], " (", ineq, verNotGH[ma2][hasVer2], ")")
#     pkgDT[which(!isGH)[ma2][hasVer2], packageFullName := newPFN]
#     pkgDT <- pkgDT[-which(isGH)[hasVer2]]
#     pkgDT[["packageFullName"]]
#   })
# }

canClone <- function(ip) {
  RversionDot <- RversionDot()
  correctBuilt <- correctBuilt(ip, RversionDot)
  # correctBuilt <- Rversion == BuiltVersion # needs to re-add package_version to both those functions
  keepCols6 <- correctBuilt %in% TRUE
  # if (isWindows()) {
  keepCols6 <- keepCols6 & ip[,  "NeedsCompilation"] == "no"
  # }
  ip[keepCols6 %in% TRUE, , drop = FALSE]
}

cantClone <- function(ip) {
  RversionDot <- RversionDot()
  correctBuilt <- correctBuilt(ip, RversionDot)
  ip[(ip[, "NeedsCompilation"] == "yes" | correctBuilt %in% FALSE)  %in% TRUE, , drop = FALSE]
}

RversionDot <- function() {
  # This adds a trailing "." so that it is e.g., 4.3., not 4.3  because
  paste0(paste(R.version$major,
               gsub("^(.{1,2})\\..+$", "\\1", R.version$minor),
               sep = "."), ".")
}

BuiltVersion2 <- function(ip) {
  paste(gsub("^(.{1,2}\\..{1,2})\\..+$", "\\1", ip[, "Built"]),
        sep = ".")
}

correctBuilt <- function(ip, RversionDot) {
  startsWith(ip[, "Built"], prefix = RversionDot)
}

colsOfDeps <- c("Depends", "Imports", "LinkingTo", "Remotes", "Suggests")


matchWithOriginalPackages <- function(pkgDT, packages) {
  # might be missing `require` column
  colsToKeep <- intersect(colnames(pkgDT), c("Package", "loadOrder", "require"))
  packagesDT <- pkgDT[, ..colsToKeep]
  origPkgNames <- extractPkgName(packages)
  # Trigger join when: (a) pkgDT has transitive deps not in the user's list, OR
  # (b) some originally-requested package is absent from pkgDT (e.g. excluded
  #     because its version constraint could not be satisfied).
  if (isTRUE(!all(pkgDT[["packageFullName"]] %in% packages)) ||
      !all(origPkgNames %in% pkgDT$Package)) {
    # Some packages will have disappeared from the pkgDT b/c of trimRedundancies
    # or unsatisfiable version constraints. Right-join ensures every originally
    # requested package appears in the result.
    packagesDT <- unique(packagesDT, on = "Package")[
      toPkgDT(packages)[, c("Package", "packageFullName")], on = c("Package")]
    # Packages absent from pkgDT get require = NA from the join; coerce to FALSE
    # so callers receive a clean logical vector (not NA or logical(0)).
    if ("require" %in% names(packagesDT))
      set(packagesDT, which(is.na(packagesDT$require)), "require", FALSE)
  }
  unique(packagesDT)[]
}


appendInstallResult <- function(pkgDT, rowsToUpdate, installResult, sep = "; ") {
  if (length(rowsToUpdate)) {
    prevInstallResult <- pkgDT$installResult[rowsToUpdate]
    ir <- installResult # needed for next line s
    pkgDT[rowsToUpdate, `:=`(installResult = ir)]
    if (!is.null(prevInstallResult)) {
      nas <- is.na(prevInstallResult)
      if (any(!nas)) {
        pkgDT[rowsToUpdate[which(!nas)],
          installResult := paste(installResult, prevInstallResult[which(!nas)], sep = sep)]
      }
    }
  }
  pkgDT

}

packageHasError <- function(libs = .libPaths(), packages = NULL) {
  problems <- Map(lib = libs, function(lib) {
    pkgs <- dir(lib)

    # need to intersect packages with pkgs
    if (!is.null(packages))
      pkgs <- intersect(packages, pkgs)

    suppressWarnings(
      out <- Map(pkg = pkgs, function(pkg) {
        f <- system.file("Meta", "package.rds", package = pkg, lib.loc = lib)
        tryCatch({readRDS(f); FALSE}, error = function(e) TRUE)
      })
    )
    out <- out[unlist(out)]
    names(out)
  })
  if (!is.null(packages)) {
    problems <- Map(p = problems, function(p) p[p %in% packages])
  }
  problems
}

binOrSrc <- function(areBinary) {
  c("src", "bin")[areBinary + 1]
}

#' @include messages.R
removeHEADpkgsIfNoUpdateNeeded <- function(pkgInstall, verbose = getOption("Require.verbose")) {
  # needs both Version and VersionOnRepos
  if (any(pkgInstall[[hasHEADtxt]])) {
    hasHead <- pkgInstall[[hasHEADtxt]]
    notGH <- !pkgInstall$isGitPkg
    whNoKeep <- which(hasHead & notGH)
    if (length(whNoKeep)) {
      # Only keepForUpdate if it is identical ... a HEAD could be of a different branch and thus a newer or older version
      pkgInstall[whNoKeep, keepForUpdate := compareVersion2(Version, VersionOnRepos, "<") | forceInstall]
      # pkgInstall[whHasHead & whGH, DESCRIPTIONFileVersionV(DESCFile)]
      # The next logical has 2 options:
      #    NAs include packages that were not "HEAD", but are needed by the "HEAD", but aren't already installed
      #    TRUE is needs installing
      #    FALSE is no need installing
      whDontInstall <- which(pkgInstall$keepForUpdate %in% FALSE)
      if (length(whDontInstall)) {
        set(pkgInstall, whDontInstall, "needInstall", .txtDontInstall)
      }
    }

  }
  pkgInstall
}



#' download.files or install.packages in a separate process
#'
#' This uses `sys` package so that messaging can be controlled. This also provides
#' the option to parallelize by spawning multiple `background` process to allow
#' parallel e.g., downloads. Noting that if `libcurl` is installed (and detected
#' using `capabilities("libcurl")`), then no explicit parallelism will be allowed,
#' instead `method = "libcurl"` will be passed enabling parallel downloads.
#'
#'
#' @param args A list with all arguments for a do.call to either `download.file,
#'        `install.packages` or a custom other function e.g., `downloadAndBuildToLocalFile`.
#' @param splitOn A character vector of the names in `args` to parallelize over.
#'        Defaults to `pkgs`. All other named elements in `args` will be assumed to
#'        be length 1 and used for every parallel process.
#' @param doLine A character string with the `"outfiles <- do.call(..., args)"` line.
#' @param returnOutfile A logical. If `TRUE`, then the names of the `outfiles` will
#'        be returned.
#' @param doLineVectorized A logical. If `TRUE`, and parallism is being used, this
#'        indicates that the `doLine` is a function that allows for multiple elements
#'        in `args[[splitOn[[1]]]`. If `FALSE`, the function will make multiple
#'        sequential calls within each parallel process to the `doLine` call.
#' @param tmpdir A single path where all downloads will be put
#' @inheritParams Require
#' @return Mostly for side effects, namely installed packages or downloaded packages or
#'        files. However, in the case of `returnOutfile = TRUE`, then a list of
#'        filenames will be returned with any outputs from the `doLine`.
sysInstallAndDownload <- function(args, splitOn = "pkgs",
                                  doLine = "outfiles <- do.call(download.packages, args)",
                                  returnOutfile = FALSE, doLineVectorized = TRUE,
                                  tmpdir, libPaths, verbose) {
  downPack <- grepl("download.packages", doLine)
  downFile <- grepl("download.file", doLine)
  installPackages <- grepl("install.packages", doLine)
  downAndBuildLocal <- grepl("downloadAndBuildToLocalFile", doLine)
  downOther <- downPack %in% FALSE & downFile %in% FALSE
  argsOrig <- args
  if (downPack %in% TRUE || downFile %in% TRUE || installPackages %in% TRUE) {
    args$method <- if (isTRUE(capabilities("libcurl"))) "libcurl" else "auto"
    if (!args$method %in% "libcurl")
      doLineVectorized <- FALSE
  }
  vecList <- splitVectors(args, splitOn, method = args$method, installPackages)
  # if (installPackages)
  #   libTemps <- lapply(vecList, function(x) tempdir3())


  pids <- numeric(length(vecList))
  outfiles <- lapply(pids, function(i) {
    fn1 <- file.path(tmpdir, basename(tempfile(fileext = ".rds")))
    normalizePath(fn1, winslash = "/", mustWork = FALSE)
  })

  doLineOrig <- doLine
  repos <- paste(args$repos, collapse = ", ")
  preMess <- if (installPackages) {
    "\n  -- To install: "
  } else {
    paste0("  -- Downloading", ifelse(nzchar(repos), paste0(" (from ", repos, ")"), ""), ":\n")
  }
  fullMess <- character() # this will accumulate and regularly clear
  for (j in seq_along(vecList)) {
    st <- Sys.time()
    i <- vecList[[j]]
    fn <- file.path(tmpdir, basename(tempfile(fileext = ".rds")))
    fn <- normalizePath(fn, winslash = "/", mustWork = FALSE)

    for (jjj in splitOn)
      args[[jjj]] <- argsOrig[[jjj]][i]

    if (doLineVectorized %in% FALSE && length(args[[splitOn[1]]]) > 1) {
      doLine <- updateDoLine(tmpdir, splitOn, doLineOrig)
    }
    # if (installPackages) {
    #   args$destdir <- args$lib <- libTemps[[j]]
    # }
    saveRDS(args, file = fn)

    cmdLine <- buildCmdLine(tmpdir, fn, doLine, downAndBuildLocal = downAndBuildLocal,
                            outfile = outfiles[j], libPaths = libPaths)

    fullMess <- makeFullMessage(fullMess, args, installPackages, downPack, downFile)

    if (installPackages)
      logFile <- basename(tempfile2(fileext = ".log")) # already in tmpdir

    if (installPackages) {
      if (length(fullMess) && nzchar(trimws(fullMess))) {
        mess <- messInstallingOrDwnlding(preMess, fullMess)
        mess <- paste0WithLineFeed(mess)
        messageVerbose(mess, verbose = verbose)
        fullMess <- character()
      }
    } else {
      fullMess <- paste0WithLineFeed(gsub("\\s,", ",", gsub("\n", " ", fullMess)))
    }

    pids[j] <- sysDo(installPackages, cmdLine, logFile, verbose)
  }

  if (installPackages %in% FALSE)
    if (length(fullMess))
      messageVerbose(messInstallingOrDwnlding(preMess, fullMess), verbose = verbose)

  on.exit(sapply(pids, tools::pskill))
  isRstudio <- isRstudio()

  fullMess <- character()
  preMess <- if (installPackages) "" else "Downloaded: "

  ll <- list()
  for (pid in pids) {
    whMatch <- match(pid, pids)
    st <- Sys.time()
    if (!installPackages) {
      spinnerOnPid(pid, isRstudio, st, verbose)
    }

    whPid <- match(pid, pids)
    if (downPack || installPackages) {
      pkgs <- argsOrig$pkgs[vecList[[whPid]]]
      if (installPackages) {
        # check installations
        if (!file.exists(logFile))
          file.create(logFile)
        log <- readLines(logFile) # won't exist if `verbose < 1`
        if (any(grepl(paste(.txtInstallationNonZeroExit, .txtInstallationPkgFailed,
                            "lazy loading failed", sep = "|"), log))) {
          return(logFile)
        }
        aa <- Map(p = args$pkgs, function(p) as.character(packVer(p, args$lib)))
        # aa <- Map(p = args$pkgs, function(p) packVer(package = p, args$lib))
        dt <- data.table(pkg = names(aa), vers = unlist(aa, use.names = FALSE), versionSpec = args$available[, "Version"])
        # the "==" doesn't work directly because of e.g., 2.2.8 and 2.2-8 which should be equal
        whFailed <- tryCatch(!compareVersion2(dt$vers, dt$versionSpec, inequality = "=="),
                             error = function(e) rep(FALSE, NROW(dt)))
        whFailed <- whFailed %in% TRUE
        if (isTRUE(any(whFailed))) {
          pkgsFailed <- dt$pkg[whFailed]
          pkgs <- setdiff(pkgs, pkgsFailed)
        }
      }
      mess <- "\n"
    } else if (downFile) {
      mess <- paste(extractPkgName(filenames = basename(argsOrig$url[vecList[[whPid]]])), collapse = comma)
    } else {
      w <- vecList[[whPid]]
      mess <- paste0(argsOrig$Account[w], "/", argsOrig$Repo[w], "@", argsOrig$Branch[w], collapse = comma)
    }

    fullMess <- if (length(fullMess)) paste(fullMess, mess, sep = ", ") else mess
    if (!installPackages) {
      ll[[whMatch]] <- try(readRDS(outfiles[[whMatch]]))
      if (!is(ll[[whMatch]], "try-error")) {
        if ( # (Sys.time() - st) > 2 &&
          nzchar(fullMess)) {
          fullMess <- paste0WithLineFeed(fullMess)
          messageVerbose(blue(paste0("  ", preMess, fullMess)), verbose = verbose)
          fullMess <- character()
        }
      }
    }
  }

  # if (installPackages) {
  #   sup <- Map(libFrom = libTemps, function(libFrom) {
  #     linkOrCopyPackageFilesInner(dir(libFrom), fromLib = libFrom, toLib = argsOrig$lib)
  #   })
  #
  # }

  # Do it again, in case the first one was in the loop; this maybe isn't necessary

  if (!is(ll, "try-error")) {# && any(!isError)) {
    # if (!is(ll, "try-error") && any(!isError)) {
    if (length(fullMess) && nzchar(fullMess)) {
    fullMess <- gsub("\n", " ", fullMess)
    fullMess <- paste0WithLineFeed(fullMess)
    messageVerbose(blue(paste0("  ", preMess, fullMess)), verbose = verbose)
    }
  }

  if (downPack && !downFile && !installPackages) {
    if (downPack)
      dt <- as.data.table(do.call(rbind, ll))
    else
      dt <- as.data.table(cbind(Package = argsOrig[["Package"]], do.call(rbind, ll)))
    setnames(dt, new = c("Package", "localFile"))
  } else if (downFile) {
    dt <- list(Package = extractPkgName(filenames = basename(argsOrig$destfile)),
               localFile = argsOrig$destfile) |> as.data.table()
    # if (any(isError)) {
      if (any(vapply(ll, grepl, pattern = "cannot open URL", FUN.VALUE = logical(1)))) {
        setOfflineModeTRUE(verbose = verbose)
    } # else {
    # pull the plug and run without sys -- this seems to be failing on GA
    # eval(parse(text = doLine))
    # }
    # }
    # return(dt)
  } else if (downAndBuildLocal) {
    localFile <- vapply(ll, function(x) if (length(x) > 0) x[[1]] else "", character(1))
    dt <- list(Package = argsOrig[["Package"]],
               localFile = localFile) |> as.data.table()
  } else  { # installPackages and Other -- expecting on logFile as a character string
    dt <- logFile
  }

  # Capture flaky connections
  fe <- tryCatch(file.exists(args$destfile), silent = TRUE, error = function(x) NULL)

  if (!is.null(fe)) {
    fs <- file.size(args$destfile[fe])
    minFileSize <- 100
    if (any(!fe) || any(fs < minFileSize)) {
      args2 <- args
      getRidOf <- !(fe & (file.size(args$destfile) >= minFileSize))
      args2$url <- args$url[getRidOf]
      args2$destfile <- args$destfile[getRidOf]
      # don't need to add anything to dt because dt is already the complete list; this will just
      #  make sure it is filled up with existing objects
      out22 <- sysInstallAndDownload(args = args2, splitOn, doLine, returnOutfile,
                                     doLineVectorized, tmpdir = tmpdir, libPaths = libPaths, verbose = verbose)
    }
  }


  dt
}

rmErroredPkgInstalls <- function(logFile, toInstall, verbose) {
  if (is.null(logFile))
    logFile <- dir(pattern = "\\.log")
  if (!is.null(logFile)) {
    if (isTRUE(file.exists(logFile))) {
      rl <- readLines(logFile)
      errs <- c("Makefile.+Error 1", "ERROR:")
      errsGrep <- paste(errs, collapse = "|")
      anyErrors <- grep(paste("Execution halted", errsGrep, sep = "|"), rl)
      if (length(anyErrors)) {
        ERRORLine <- lapply(errs, grep, x = rl, value = TRUE)
        if (length(unlist(ERRORLine))) {
          pkgFail1 <- extractPkgNameFromWarning(ERRORLine[[2]])
          rmFiles <- character()
          if (length(pkgFail1)) {
            DONELines <- grep("\\* DONE \\(", rl, value = TRUE)
            pkgSucceed <- gsub("\\* DONE \\((.+)\\)", "\\1", DONELines)
            # ipa$pkgs <- setdiff(ipa$pkgs, pkgSucceed)
            # ipa$available <- ipa$available[!ipa$available[, "Package"] %in% pkgSucceed, , drop = FALSE]
            toInstall2 <- toInstall[!toInstall[["Package"]] %in% pkgSucceed]
            rmFiles <- c(rmFiles, toInstall2[["localFile"]], basename(toInstall2[["localFile"]]))
          }
          pkgFail2 <- gsub("^.+[[:digit:]]: (.+).ts.+$", "\\1", ERRORLine[[1]])
          pkgFail2 <- setdiff(pkgFail2, pkgFail1)
          if (length(pkgFail2)) {
            toInstall2 <- toInstall[toInstall[["Package"]] %in% pkgFail2]
            rmFiles <- c(rmFiles, toInstall2[["localFile"]], basename(toInstall2[["localFile"]]))
          }

          #messageVerbose("Because of ERROR, removing Cached copy of ", paste(toInstall2[["packageFullName"]], collapse = ", "),
          #               verbose = verbose)

          # unlink(rmFiles)
        }
      }
    }
  }
}


archiveDownloadSys <- function(pkgArchOnly, whNotfe, tmpdir, verbose) {
  set(pkgArchOnly, whNotfe, "fileExists", FALSE)
  pkg <- split(pkgArchOnly, by = "fileExists")
  p <- pkg[["FALSE"]]
  set(p, NULL, "Repository", file.path(contrib.url(p$repo, type = "source"), .txtArchive))
  # p[, Repository := file.path(contrib.url(repo, type = "source"), .txtArchive)]
  p$repo <- naToEmpty(p$repo)
  p$PackageUrl <- naToEmpty(p$PackageUrl)
  url <- if (any(greplV(unique(p$repo), p$PackageUrl))) {
    p$PackageUrl
  } else {
    file.path(p$Repository, p$PackageUrl)
  }
  nonEmpties <- which(nchar(url) > 0)
  if (length(nonEmpties)) {
    args <- list(url = url[nonEmpties],
                 destfile = file.path(tmpdir, basename(url)[nonEmpties]))
    dt <- sysInstallAndDownload(args = args, splitOn = c("url", "destfile"),
                                doLine = "outfiles <- do.call(download.file, args)",
                                tmpdir = tmpdir,
                                verbose = verbose)
    p[dt, localFile := i.localFile, on = "Package"]
    p[, installFrom := .txtLocal]
    p[, newLocalFile := TRUE]
  }
  pkg[["FALSE"]] <- p
  pkgArchOnly <- rbindlist(pkg, use.names = TRUE, fill = TRUE)
  set(pkgArchOnly, NULL, "fileExists", NULL)
  pkgArchOnly[]
}


naToEmpty <- function(vec) {
  naToEmpty <- is.na(vec)
  if (any(naToEmpty)) vec[naToEmpty] <- ""
  vec
}

#' @importFrom stats setNames
splitVectors <- function(argsOrig, splitOn, method, installPackages) {
  if (identical(method, "libcurl") || isTRUE(installPackages)) {
    vecList <- list(seq_along(argsOrig[[splitOn[1]]]))
  } else {
    vec <- seq_along(argsOrig[[splitOn[1]]])
    chunk2 <- function(x, n) {
      if (n == 1)
        list(x) |> setNames("1")
      else
        split(x, cut(seq_along(x), n, labels = FALSE))
    }
    vecList <- chunk2(vec, min(length(argsOrig[[splitOn[1]]]), min(8, getOption("Ncpus"))))
  }
}

spinnerOnPid <- function(pid, isRstudio, st, verbose) {
  # if (isRstudio %in% FALSE) {
  #    messageVerbose(".", verbose)
  #  } else {
  aa <- NA
  spinner <- "|"
  mess <- if (verbose > 1) " \n" else " "

  messageVerbose(mess, verbose = verbose)
  if (Sys.time() - st > 1)
    messageVerbose("  \b\b ...  ", verbose = verbose)
  while (is.na(aa)) {
    aa <- sys::exec_status(pid, wait = FALSE)
    Sys.sleep(0.05)
    spinner <- ifelse (spinner == "|", "/",
                       ifelse(spinner == "/", "-",
                              ifelse(spinner == "-", "\\",
                                     ifelse(spinner == "\\", "|"))))
    if (Sys.time() - st > 1) {
      mess <- paste0("\b\b", spinner)
      messageVerbose(mess, verbose = verbose)
    }
  }
  messageVerbose("\b\b\b", verbose = verbose)
  # }

}


sysDo <- function(installPackages, cmdLine, logFile, verbose) {
  Rscript <- file.path(R.home("bin"), "Rscript")
  if (installPackages) {
    # if (isWindows())
    #   messageVerbose("  -- ", .txtInstallingColon,"\n", verbose = verbose, appendLF = FALSE)
    pid <- sys::exec_wait(
      Rscript, cmdLine,
      std_out = function(x) {
        mess <- rawToChar(x)
        msgStdOut(mess, logFile, verbose)
      },
      std_err = function(x) {
        mess <- rawToChar(x)
        msgStdErr(mess, logFile, verbose)
      }
    )

  } else {
    cmd <- sys::exec_background
    pid <- cmd(
      Rscript, cmdLine,
      std_out = installPackageVerbose(verbose, verboseLevel = 2),
      std_err = installPackageVerbose(verbose, verboseLevel = 2)
    )
  }
  pid
}

buildCmdLine <- function(tmpdir, fn, doLine, downAndBuildLocal, outfile, libPaths) {
  o <- options()[c("HTTPUserAgent", "Ncpus")]
  tf <- file.path(tmpdir, basename(tempfile(fileext = ".rds")))
  tf <- normalizePath(tf, winslash = "/", mustWork = FALSE)
  saveRDS(o, file = tf)

  ar <- c(paste0("o <- readRDS('",tf,"')"),
          "options(o)",
          paste0("setwd('", normalizePath(getwd(), winslash = "/", mustWork = FALSE),"')"),
          paste0("args <- readRDS('", fn, "')"),
          doLine,
          paste0("saveRDS(outfiles, '",outfile,"')"))

  if (downAndBuildLocal) { # because only it uses Require in the spawned R system
    hasRequireDepsInstalled <- dir(libPaths)#, pattern = "Require")
    grp <- paste0("^", c(.RequireDependenciesNoBase, "gitcreds"), "$", collapse = "|")
    installed <- grep(grp, hasRequireDepsInstalled, value = TRUE)
    notInstalled <- setdiff(.RequireDependenciesNoBase, installed)
    if (length(notInstalled)) {
      # warning is about restart R; not relevant here
      suppressWarnings(Install(notInstalled, verbose = -2, libPaths = libPaths[1]))
    }
    ar <- c(paste0(".libPaths('", libPaths[1], "')"), ar)
  }

  cmdLine <- unlist(lapply(ar, function(x) c("-e", x)))
  cmdLine
}

updateDoLine <- function(tmpdir, splitOn, doLineOrig) {
  tf3 <- file.path(tmpdir, basename(tempfile(fileext = ".rds")))
  tf3 <- normalizePath(tf3, winslash = "/", mustWork = FALSE)
  saveRDS(splitOn, file = tf3)
  doLine <-
    paste0("splitOn <- readRDS('",tf3,"')
                   a <- try(lapply(seq_along(args[[splitOn[1]]]), function(ind) lapply(args[splitOn], '[[', ind)))
                   extraArgs <- setdiff(names(args), splitOn)
                   for (ii in seq_along(a)) for (ea in extraArgs) a[[ii]][[ea]] <- args[[ea]]
                   try(outfiles <- lapply(a, function(args) ", doLineOrig, "))")
  doLine <- strsplit(doLine, "\n")[[1]]
  doLine
}

makeFullMessage <- function(fullMess, args, installPackages, downPack, downFile) {

  if (downPack || installPackages) {
    mess <- paste(args$pkgs, collapse = comma)
  } else if (downFile) {
    mess <- paste(extractPkgName(filenames = basename(args$url)), collapse = comma)
  } else {
    mess <- paste0(args$Account, "/", args$Repo, "@", args$Branch)
  }

  fullMess <- if (length(fullMess)) paste(fullMess, mess, sep = ", ") else mess
  fullMess
}

downloadedInSeconds <- function(et) {
  msgEndsInSeconds("packages downloaded in ", et)
  # paste("packages downloaded in ", format(et, digits = 2), " seconds.")
}

msgEndsInSeconds <- function(mess, et) {
  paste0(mess, format(et, digits = 2), " seconds.")
}

messInstallingOrDwnlding <- function(preMess, fullMess) {
  greyLight(paste0(preMess, paste(fullMess, collapse = ", ")))
}


alreadyExistingDESCFile <- function(libPaths, Repo, Account, Branch, installResult, verbose) {
  alreadyExistingDESCRIPTIONFile <- file.path(libPaths[1], Repo, "DESCRIPTION")
  SHAonGH <- getSHAfromGitHubMemoise(repo = Repo, acct = Account, br = Branch, verbose = verbose)
  if (file.exists(alreadyExistingDESCRIPTIONFile)) {
    SHAonLocal <- DESCRIPTIONFileOtherV(alreadyExistingDESCRIPTIONFile, other = "GithubSHA1")
    SHAonLocal <- unique(SHAonLocal)[1] # seems like it can be >1, which is an error
    # SHAonGH <- if (identical(SHAonGH, SHAonLocal)) FALSE else SHAonGH
    if (identical(SHAonGH, SHAonLocal)) {
      messageVerbose(msgShaNotChanged(Account, Repo, Branch),
                     verbose = verbose, verboseLevel = 1
      )
      installResult <- .txtShaUnchangedNoInstall
    }
  } else {
    SHAonLocal <- ""
  }
  list(SHAonLocal, SHAonGH, installResult)
}

avokto <- function(versionSpec, VersionOnRepos, inequality) {
  isHEAD <- versionSpec == "HEAD"
  if (isTRUE(any(isHEAD %in% TRUE))) {
    avok <- rep(TRUE, length(versionSpec))
    avokto <- avok
  }
  if (isTRUE(any(isHEAD %in% FALSE))) {
    out <- Map(vor = VersionOnRepos[!isHEAD], function(vor) any(!compareVersion2(vor, versionSpec, inequality) %in% FALSE))
    avokto <- Map(vor = VersionOnRepos[!isHEAD], function(vor) {
      all(compareVersion2(vor, versionSpec, inequality) %in% TRUE)
    })
    avok <- any(unlist(out))
  }

  list(availableVersionOK = avok, availableVersionOKthisOne = unlist(avokto))
}

packVer <- function(package, lib.loc = .libPaths()[1]) {
  DESCRIPTIONFileVersionV(file.path(lib.loc, package, "DESCRIPTION"))
}


recoverFromFail <- function(toInstallOut, toInstall, ipa, attempt, tries, repos,
                            tmpdir, libPaths, verbose, returnDetails) {
  if (attempt <= max(tries)) {
    pkgName <- strsplit(toInstallOut, .txtPkgFailed)[[1]][2]
    pkgName <- strsplit(pkgName, .txtRetrying)[[1]][1]
    if (isTRUE(!is.na(pkgName))) { #
      fileToDelete <- toInstall$localFile[toInstall$Package %in% pkgName]
      if (isTRUE(file.exists(fileToDelete)))
        # unlink(fileToDelete)

      pkgInstall <- doDownloads(toInstall[Package %in% basename(pkgName)], repos = repos,
                                purge = FALSE, verbose = verbose,

                                tmpdir = tempdir(), libPaths = libPaths[1])
      doneUpTo <- which(rownames(ipa$available) == pkgName)
      keep <- seq(min(NROW(ipa$available), doneUpTo[1]),
                  NROW(ipa$available))
      newAv <- ipa$available[keep, , drop = FALSE]
      ipa$available <- newAv
      ipa$pkgs <- ipa$pkgs[keep]
      # try(Install(toInstall[Package %in% basename(pkgName)][["packageFullName"]],
      #             verbose = verbose - 1, returnDetails = returnDetails,
      #             dependencies = FALSE)) # dependencies = FALSE b/c it should already have what it needs
    } else {
      stop(toInstallOut)
    }
  } else {
    stop("Errors installing packages, likely due to corrupt local cache files; ",
         "attempts were made to remove the corrupt cache entries. \n",
         "Try rerunning again.")
  }
  ipa
}


clearErrorReadRDSFile <- function(mess, libPath = .libPaths()[1]) {
  if (length(mess)) {
    if (isTRUE(any(grepl("Error in readRDS", mess)))) {
      dd <- dir(libPath, full.names = TRUE)
      a <- lapply(grep("_cache$", dd, value = TRUE, invert = TRUE), function(d)
        tryCatch(readRDS(file.path(d, "Meta", "package.rds")), silent = TRUE,
                 error = function(e) {print(d); unlink(d, recursive = TRUE)}))
    }
  }
}


# A package can end up installed at a version that satisfies the request while an
# OLDER, insufficient version is still LOADED in the session -- e.g. a dependency
# (reproducible) pulled in via another package's `Imports` from a different
# library before the satisfying version was installed. R cannot hot-swap a loaded
# namespace, so the session keeps using the stale version (and `find.package()`
# returns its path) until R is restarted. Flag those rows with an installResult
# containing "restart" so the existing "Please restart R" warning fires -- without
# this, Require silently installs the new version and the user keeps running the
# old one. Fires whenever the LOADED version fails the constraint but the on-disk
# (installed) version meets it, regardless of whether the install happened this
# call (covers a satisfying version already on disk from a prior run too).
flagRestartForLoadedInsufficient <- function(pkgDT) {
  if (!NROW(pkgDT) ||
      !all(c("Package", "versionSpec", "inequality", "Version") %in% names(pkgDT)))
    return(pkgDT)
  loaded <- setdiff(loadedNamespaces(), .basePkgs)
  if (!length(loaded)) return(pkgDT)
  for (i in seq_len(NROW(pkgDT))) {
    pkg <- pkgDT[["Package"]][i]
    if (!pkg %in% loaded) next
    vSpec <- pkgDT[["versionSpec"]][i]
    ineq  <- pkgDT[["inequality"]][i]
    if (is.na(vSpec) || !nzchar(vSpec) || is.na(ineq) || !nzchar(ineq)) next
    loadedVer <- tryCatch(as.character(getNamespaceVersion(pkg)),
                          error = function(e) NA_character_)
    if (is.na(loadedVer) || !nzchar(loadedVer)) next
    if (isTRUE(compareVersion2(loadedVer, vSpec, ineq))) next     # loaded already OK
    instVer <- pkgDT[["Version"]][i]
    if (!is.na(instVer) && nzchar(instVer) && !identical(loadedVer, instVer) &&
        isTRUE(compareVersion2(instVer, vSpec, ineq))) {
      set(pkgDT, i, "installResult",
          paste0("Need to restart R (loaded ", loadedVer, " < required ",
                 ineq, " ", vSpec, "; ", instVer, " installed)"))
    }
  }
  pkgDT
}

needToRestartR <- function(pkgDT) {
  whNeedInstall <- pkgDT[["needInstall"]] %in% .txtInstall
  toRm <- pkgDT[["Package"]][whNeedInstall] %in% extractPkgName(unlist(.RequireDependencies))
  if (any(toRm)) {
    NeedRestart <- if (getOption("Require.installPackagesSys") > 0) {
      # Can install when in a different process
      pkgDT[["Package"]][whNeedInstall][toRm] %in% "Require"
    } else {
      FALSE
    }

    whRm <- which(whNeedInstall)[toRm]

    # Try to install them anyway, but it will fail and report error
    # set(pkgDT, whRm, "needInstall", .txtDontInstall)

    set(pkgDT, whRm, "installed", TRUE)
    set(pkgDT, whRm, "installResult", "Can't install Require dependency")
    if (any(NeedRestart))
      set(pkgDT, whRm, "installResult", "Need to restart R")
  }
  pkgDT
}


checkCompileFailedThenInstallBinary <- function(rl, ipa, toInstall, verbose) {
  makefileErrorGrep <- "Makefile:.+:.+\\.ts] Error 1"
  makefileErrorGsub <- "^.+Makefile:.+: (.+)\\.ts.+Error.*$"
  startLine4 <- grep(makefileErrorGsub, rl)
  startLine <- grep("installation of one or more packages failed", rl)
  startLine2 <- grep("non-zero", rl)
  compFailed <- "compilation failed for package"
  startLine3 <- grep(compFailed, rl)
  pkgs <- rl[c(startLine+1, startLine2, startLine3, startLine4)]
  # pkgs <- rl[startLine+1]
  if (length(pkgs)) {
    pkgs2 <- strsplit(gsub("^.+package '(.+)' had non.+$", "\\1", rl[startLine2]), split = "', '") |>
      unlist()
    pkgs1 <- strsplit(gsub("^.*probably '", "", rl[startLine + 1]), split = "', '")
    startLine3a <- unlist(lapply(startLine3, function(x) x:(x+ 1))) # can be line wrapped
    startLine3b <- grep("'.+'|\u2018.+\u2019", rl[startLine3a], value = TRUE)
    startLine3c <- grep("removing", startLine3b, invert = TRUE, value = TRUE)
    pkgs3 <- gsub("^.+('|\u2018)(.+)('|\u2019).*$", "\\2", startLine3c)
    # pkgs3 <- gsub(paste0("^.+", compFailed, ".+'(.+)'.*$"), "\\1", rl[startLine3])
    # pkgs3 can be wrapped onto next line; so unreliable
    # startLine3b <- grep("removing", rl)
    # pkgs3 <- gsub("^.+removing.*'(.+)'", "\\1", rl[startLine3b]) |> basename()
    pkgs4 <- gsub(makefileErrorGsub, "\\1", rl[startLine4])
    if (length(pkgs1)) {
      pkgs1 <- gsub("'$", "", pkgs1[[1]])
    }

    if (length(pkgs3) == 0) {
      pkgsFailedCompile <- unique(c(pkgs4))
    } else {
      pkgsFailedCompile <- unique(c(pkgs3))
    }
    failedToCompileOrDownstream <- toInstall[Package %in% pkgsFailedCompile]
    # failedToCompileOrDownstream <- toInstall[Package %in% unique(c(pkgs1, pkgs2, pkgs3))]
    failedPkgs <- failedToCompileOrDownstream[!isGitPkg %in% TRUE]$Package
    # ipa$available[ipa$available[, "NeedsCompilation"] %in% "yes" & ipa$available[, "Package"] %in% failedPkgs,]
    # failedBcCompilerFail <- ipa$available[ipa$available[, "NeedsCompilation"] %in% "yes", ]
    if (length(failedPkgs)) {
      if (isWindows()) {
        messageVerbose("Require encountered package(s) that cannot be compiled; ",
                       "installing the binary which will mean it may not be the expected version", verbose = verbose)
        typeHere <- "binary"
        ap <- available.packagesCached(type = typeHere)
        apFailed <- ap[Package %in% failedPkgs]
        # apFailed[, "NeedsCompilation"] %in% "yes"
        # do.call(install.packages, ipa)
        install.packages(apFailed[["Package"]], type = typeHere, dependencies = FALSE)
      } else {
        # try binary
        ipa2 <- ipa
        ipa2$pkgs <- failedPkgs
        ipa2$available <- ipa$available[ipa$available[, "Package"] %in% failedPkgs,,drop = F]

        install.packages(failedPkgs, repos = positBinaryRepos(), dependencies = FALSE)
      }
    }
    ip <- .installed.pkgs(purge = TRUE) |> as.data.table()
    ipa$pkgs <- ipa$pkgs[!ipa$pkgs %in% ip$Package]
    ipa$available <- ipa$available[!ipa$available[, "Package"] %in% ip$Package, , drop = FALSE]
    # ipa$available <- ipa$available[!ipa$available[, "Package"] %in% apFailed[["Package"]], ]
    # ipa$pkgs <- ipa$available[, "Package"]
  }
  ipa
}

#' @importFrom data.table copy
cleanUpRecursivePkgVersionIssues <- function(deps2, verbose) {
  if (isTRUE(any(deps2$inequality %in% "==")) &&
      isTRUE(!is.null(deps2[["parentVersionQueried"]]))) {
    set(deps2, NULL, "ord", seq_len(NROW(deps2)))
    depsTmp <- deps2[, .(Package = extractPkgName(parentPackage),
                         packageFullName = parentPackage,
                         Version = parentVersionQueried,
                         ord = ord)]
    depsTmp2 <- depsTmp[!is.na(Package)]
    depsTmp3 <- parsePackageFullname(data.table::copy(depsTmp2))#[Package %in% "lme4"]
    a <- data.table::copy(deps2)
    set(a, NULL, "ordA", seq_len(NROW(a)))
    for (i in 1:3) {
      b <- confirmEqualsDontViolateInequalitiesThenTrim(a, verbose = verbose - 1 )
      set(b, NULL, "ordB", seq_len(NROW(b)))
      parents <- b[!is.na(parentPackage),
                   .(packageFullName = paste0(extractPkgName(parentPackage),
                                              " (==", parentVersionQueried, ")"),
                     Package = extractPkgName(parentPackage),
                     ord = ord,
                     ordB = ordB)]
      parentsDT <- parsePackageFullname(parents)
      kickOut <- b[ , {
        .pkg = Package
        parentsIndPkg <- parentsDT[Package %in% .pkg]
        out <- NULL
        if (NROW(parentsIndPkg)) {
          good <- compareVersion2(parentsIndPkg$versionSpec, versionSpec, inequality)
          out <- parentsIndPkg$ordB[!good]
        }
        out
      }, by = c("versionSpec", "Package")]
      if (NROW(kickOut) == 0) {
        break
      }
      setorderv(kickOut, "V1")
      notOK <- b[ordB %in% kickOut$V1]
      kickOutInd <- na.omit(kickOut$V1)
      kickOutIndA <- b$ordA[kickOutInd]
      notUserAsked <- is.na(kickOut$V1)
      if (any(notUserAsked)) {
        kickOutNotAsked1 <- which(b$Package %in% kickOut$Package[notUserAsked])
        kickOutNotAskedA <- b$ordA[kickOutNotAsked1]
        kickOutIndA <- sort(c(kickOutIndA, kickOutNotAskedA))
      }
      actualRmInA <- which(a$ordA %in% kickOutIndA)
      a[actualRmInA]
      a <- a[-actualRmInA]
    }

    # Final pass -- packages that are parentPackages, but that are not Packages
    toRm <- setdiff(extractPkgName(a$parentPackage), a$Package) |> na.omit()
    a <- a[!extractPkgName(parentPackage) %in% toRm]

    deps2 <- a

  }
  deps2
}

macBinaryFileExtNoDot <- "tgz"
macBinaryFileExt <- paste0(".", macBinaryFileExtNoDot)
macBinaryFileExtGrep <- gsub("\\.", "\\\\.", macBinaryFileExt)
