utils::globalVariables(c(
  "..keep", "..keep", "..keepCols", "..keepColsOuter", "..keepShort",
  "..onlyKeep", "Additional_repositories", "correctVersionInCache",
  "DESCFileFull", "descFiles", "githubPkgName", "installedSha",
  "localBranch", "localFiles", "localRepo", "localUsername", "newPackageFullName",
  "shas"
))

#' Determine package dependencies
#'
#' This will first look in local filesystem (in `.libPaths()`) and will use a
#' local package to find its dependencies. If the package does not exist
#' locally, including whether it is the correct version, then it will look in
#' (currently) `CRAN` and its archives (if the current `CRAN` version is not the
#' desired version to check). It will also look on `GitHub` if the package
#' description is of the form of a GitHub package with format
#' `account/repo@branch` or `account/repo@commit`. For this, it will attempt to
#' get package dependencies from the GitHub \file{DESCRIPTION} file. This is
#' intended to replace `tools::package_dependencies` or `pkgDep` in the
#' \pkg{miniCRAN} package, but with modifications to allow multiple sources to
#' be searched in the same function call.
#'
#' @note `tools::package_dependencies` and `pkgDep` will differ under the
#'   following circumstances: \enumerate{ \item GitHub packages are not detected
#'   using `tools::package_dependencies`; \item `tools::package_dependencies`
#'   does not detect the dependencies of base packages among themselves, *e.g.*,
#'   `methods` depends on `stats` and `graphics`. }
#'
#' @inheritParams Require
#' @param which a character vector listing the types of dependencies, a subset
#'   of `c("Depends", "Imports", "LinkingTo", "Suggests", "Enhances")`.
#'   Character string `"all"` is shorthand for that vector, character string
#'   `"most"` for the same vector without `"Enhances"`.
#' @param depends Logical. Include packages listed in "Depends". Default `TRUE`.
#' @param imports Logical. Include packages listed in "Imports". Default `TRUE`.
#' @param suggests Logical. Include packages listed in "Suggests". Default
#'   `FALSE`.
#' @param linkingTo Logical. Include packages listed in "LinkingTo". Default
#'   `TRUE`.
#' @param recursive Logical. Should dependencies of dependencies be searched,
#'   recursively. NOTE: Dependencies of suggests will not be recursive. Default
#'   `TRUE`.
#' @param keepVersionNumber Logical. If `TRUE`, then the package dependencies
#'   returned will include version number. Default is `FALSE`
#' @param libPaths A path to search for installed packages. Defaults to
#'   `.libPaths()`
#' @param sort Logical. If `TRUE`, the default, then the packages will be sorted
#'   alphabetically. If `FALSE`, the packages will not have a discernible order
#'   as they will be a concatenation of the possibly recursive package
#'   dependencies.
#' @param includeBase Logical. Should R base packages be included, specifically,
#'   those in `tail(.libPaths(), 1)`
#' @param includeSelf Logical. If `TRUE`, the default, then the dependencies
#'   will include the package itself in the returned list elements, otherwise,
#'   only the "dependencies"
#' @param simplify Logical or numeric. If `TRUE` (or > 0), the default,
#'   the return object is "just" a character vector of package names
#'   (with version requirements). If `FALSE` (or `0`),
#'   then a `data.table` will be returned with 4 columns,
#'   `Package`, `packageFullName`, `parentPackage` (the package name for which the
#'   given line entry is a dependency; will be "user" if it was user supplied)
#'   and `deps`, which is a list of `data.table`s
#'   of all dependencies. If a negative number, then it will return a similar `data.table`
#'   as with `FALSE`, however, duplications in the recursive package dependencies
#'   are left intact.
#' @param Additional_repositories Logical. If `TRUE`, then `pkgDep` will return
#'   a list of `data.table` objects (instead of character vectors)
#'   with a column `packageFullName` and possibly a second column `Additional_repositories`,
#'   which may have been specified in a `DESCRIPTION` file. NOTE: THIS ALTERS
#'   THE OUTPUT CLASS
#' @param ... Currently only `dependencies` as an alternative to `which`. If specified,
#'   then `which` will be ignored.
#'
#' @inheritParams Require
#'
#' @export
#' @rdname pkgDep
#'
#' @examplesIf Require:::.runLongExamples()
#' opts <- Require:::.setupExample()
#'
#' pkgDep("tidyverse", recursive = TRUE)
#'
#' # GitHub, local, and CRAN packages
#' pkgDep(c("PredictiveEcology/reproducible", "Require", "plyr"))
#'
#' Require:::.cleanup(opts)
pkgDep <- function(packages,
                   libPaths,
                   which = c("Depends", "Imports", "LinkingTo"),
                   recursive = TRUE,
                   depends,
                   imports,
                   suggests,
                   linkingTo,
                   repos = getOption("repos"),
                   keepVersionNumber = TRUE,
                   includeBase = FALSE,
                   includeSelf = TRUE,
                   sort = TRUE,
                   simplify = TRUE,
                   purge = getOption("Require.purge", FALSE),
                   verbose = getOption("Require.verbose"),
                   type = getOption("pkgType"),
                   Additional_repositories = FALSE, ...) {

  stPkgDep <- Sys.time()
  libPaths <- dealWithMissingLibPaths(libPaths, ...)
  packages <- parseMultiLinePackages(packages)

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

  purge <- purgePkgDep(purge)
  # purge <- dealWithCache(purge)

  stRequireEnv <- whereInStack("stRequire")
  if (!is.null(stRequireEnv))
    if (is.null(get0("stRequire", stRequireEnv))) { # if this is NOT called from Require
      on.exit(
        checkAutomaticOfflineMode() # This will turn off offlineMode if it had been turned on automatically
      )
    }

  # deps <- packages
  if (!includeBase) {
    packages <- packages[!packages %in% .basePkgs]
  }
  if (length(packages)) {
    which <- depsImpsSugsLinksToWhich(depends, imports, suggests, linkingTo, which)
    if (getOption("Require.usePak", FALSE)) {
      if (!requireNamespace("pak")) stop("Please install pak")
      log <- tempfile2(fileext = ".txt")
      withCallingHandlers(
        deps <- pakPkgDep(packages, which, simplify, includeSelf, includeBase, keepVersionNumber,
                          verbose = verbose)
        , message = function(m) {
          if (verbose > 1)
            cat(m$message, file = log, append = TRUE)
          if (verbose < 1)
            invokeRestart("muffleMessage")
        }
      )
    } else {

      # Only deal with first one of "which"...
      if (is.list(which)) which <- which[[1]]

      deps <- getPkgDeps(packages, parentPackage = "user",
                         parentPackageVersion = NA,
                         recursive = recursive, repos = repos, verbose = verbose,
                         type = type, which = which, includeBase = includeBase, libPaths = libPaths,
                         includeSelf = includeSelf, grandp = "aboveuser")
      depsCol <- "deps"
      if (!identical(which, character())) { # when which = character(), results in a NULL list element: [[1]] NULL
        set(deps, NULL, depsCol, lapply(deps[[deps(recursive)]], rbindlistRecursive))
      }
    keepCols <- c("Package", "packageFullName", "Version", "versionSpec", "inequality",
                "githubPkgName", "repoLocation", ".depth", "which", "parentPackage", "parentVersionQueried")
    dotDepth <- ".depth"

    set(deps, NULL, # whHasDeps,
        depsCol,
        Map(dep = deps[[depsCol]], self = deps[["packageFullName"]],
            # Map(dep = deps[[depsCol]][whHasDeps], self = deps[["packageFullName"]][whHasDeps],
            function(dep, self) {
              if (!is.null(dep)) {

                rmCols <- intersect(colnames(dep), c(deps(TRUE), deps(FALSE)))
                if (length(rmCols))
                  set(dep, NULL, rmCols, NULL)
                # if (dotDepth %in% colnames(dep) && !all(diff(dep[[dotDepth]]) >= 0)) {
                #   if (!exists("sorted")) sorted <<- 0
                #   sorted <<- sorted + 1
                #   setorderv(dep, cols = dotDepth)
                # } else {
                #   if (!exists("unsorted")) unsorted <<- 0
                #   unsorted <<- unsorted + 1
                # }

                keepCols <- intersect(colnames(dep), c(keepCols, .txtGitHubParsedCols))
                if (simplify >= 0)
                  dep <- dep[!duplicated(dep[["Package"]])]
                set(dep, NULL, c("packageFullName", "parentPackage"),
                    list(cleanPkgs(dep[["packageFullName"]]), cleanPkgs(dep[["parentPackage"]])))
                dep <- rmBase(includeBase, dep)
                dep <- dep[, ..keepCols] # includeSelf added some cols
                # setnames(dep, old = depsCol, new = "deps")
              }
              dep <- addSelf(includeSelf, dep, self)
              if (!is.null(dep))
                setorderv(dep, cols = c("Package", intersect(dotDepth, colnames(dep)))) # <- dep[order(dep$Package)]
              dep
            }))

    keepColsOuter <- c("Package", "packageFullName", "parentPackage", "parentVersionQueried", depsCol)
    keepColsOuter <- intersect(colnames(deps), keepColsOuter)
    deps <- deps[, ..keepColsOuter]
    # trimRedundancies is better for following the deps, but it will fail to keep original
    #   user request. Use only duplicated instead ... to keep user order

    if (keepVersionNumber %in% FALSE) {
      deps <- Map(pkgFN = deps$deps, function(pkgFN) pkgFN[["Package"]])
    } else if (simplify %in% TRUE) {
      # collapse or not to list of character vector
      pfn <- deps$packageFullName
      if (getOption("Require.offlineMode") && !depsCol %in% colnames(deps)) {
        deps <- lapply(pfn, function(x) list(NULL))
      } else {
        deps <- lapply(deps[[depsCol]], function(x) x[["packageFullName"]])
      }
      names(deps) <- pfn

    }
    }
  } else {
    deps <- toPkgDepDT(packages)
    # names(deps) <- unlist(deps)
  }

  return(deps)
}

getPkgDeps <- function(pkgDT, parentPackage, parentPackageVersion = NA, recursive, which, repos, type, includeBase,
                       includeSelf, libPaths, verbose, .depth = 0, .counter, grandp, parentChain = "") {

  if (is.list(which)) which <- which[[1]]

  cols <- c("red", "blue", "yellow", "green", "black", "turquoise", "cyan")

  lim <- 5
  if (isNotNULLAnd(.depth, .depth <= lim))
    messageVerbose(get(cols[.depth + 1])(paste(rep("  ", .depth), collapse = ""),
                                         cleanPkgs(parentPackage)),
                   verbose = .depth <= (lim ) && verbose >= 3)
  deps <- NULL

  if (NROW(pkgDT) > 0) { # this will skip any pkgDT that has no deps, breaks out of recursion
    if (is(pkgDT, "list")) pkgDT <- pkgDT[[1]]
    pkgDT <- toPkgDTFull(pkgDT)
    pkgDT <- rmRifInPackageCol(pkgDT)
    isInBase <- pkgDT$Package %in% .basePkgs
    if (!all(isInBase)) {
      pkgDTBase <- splitKeepOrderAndDTIntegrity(pkgDT, splitOn = isInBase)
      if (NROW(pkgDTBase[["FALSE"]])) {

        callerChain <- if (parentPackage %in% c("user", "aboveuser", NA_character_)) ""
                       else if (!nzchar(parentChain)) parentPackage
                       else paste0(parentPackage, " -> ", parentChain)
        pkgDTBase[["FALSE"]] <- getDeps(pkgDTBase[["FALSE"]], which, recursive = recursive,
                                        repos = repos, type = type, libPaths = libPaths, verbose = verbose,
                                        parentChain = callerChain)
        hasDeps <- sapply(pkgDTBase$`FALSE`[[depFa]], NROW) > 0

        pkgDTBase[["FALSE"]] <- forceEqualitiesIfAnyAndPoss(pkgDTBase[["FALSE"]])
        if (any(hasDeps)) {
          if (recursive %in% TRUE && any(pkgDTBase[["FALSE"]]$cachedTRUE %in% FALSE)) {

            which <- setdiff(which, "Suggests")
            pkgDTNeedRecursive <-
              splitKeepOrderAndDTIntegrity(pkgDTBase[["FALSE"]],
                                           splitOn = pkgDTBase[["FALSE"]]$cachedTRUE %in% FALSE)

            parentVersion <- rep(NA, NROW(pkgDTNeedRecursive[["TRUE"]]))
            if (is.null(pkgDTNeedRecursive[["TRUE"]]$VersionOnRepos) ||
                anyNA(pkgDTNeedRecursive[["TRUE"]]$VersionOnRepos)) {
              parentVersion <- vapply(strsplit(pkgDTNeedRecursive[["TRUE"]]$snFALSE, split = "__"),
                                      function(x) if (any(is.na(x))) x else x[[2]], FUN.VALUE = character(1))
              # parentVersion <- gsub("^.+\\_+(.+)\\_\\_+htt.+$", "\\1",
              #                       pkgDTNeedRecursive[["TRUE"]]$snFALSE)

            }
            nasPV <- is.na(parentVersion)
            if (any(nasPV)) {
              parentVersion[nasPV] <- pkgDTNeedRecursive[["TRUE"]]$VersionOnRepos[nasPV]
            }
            pkgDTNeedRecursive[["TRUE"]][[depFa]] <-
              Map(pkgDT = pkgDTNeedRecursive[["TRUE"]][[depFa]],
                  pp = pkgDTNeedRecursive[["TRUE"]]$packageFullName,
                  ppv = parentVersion, # pkgDTNeedRecursive[["TRUE"]]$VersionOnRepos,
                  grandp = grandp,
                  .counter = seq(NROW(pkgDTNeedRecursive[["TRUE"]])),
                  function(pkgDT, pp, ppv, .counter, grandp)
                    getPkgDeps(pkgDT, parentPackage = pp, parentPackageVersion = ppv,
                               grandp = parentPackage,
                               .counter = .counter,
                               recursive = recursive, which = which, repos = repos,
                               type = type, includeBase = includeBase,
                               includeSelf = includeSelf,
                               libPaths = libPaths,
                               .depth = .depth + 1, verbose = verbose,
                               parentChain = callerChain))

            pkgDTBase[["FALSE"]] <- appendRecursiveToDeps(pkgDTNeedRecursive, caTr, depFa, caFa, snFa, depTr, snTr)
            hasRecursiveTRUE <- !is.na(pkgDTBase[["FALSE"]][[snTr]])
            whHasRecursiveTRUE <- which(hasRecursiveTRUE)
            whNotHasRecTRUE <- which(!hasRecursiveTRUE)

            if (length(whNotHasRecTRUE)) {
              if (!is.null(pkgDTBase[["FALSE"]][[snFa]]))
                set(pkgDTBase[["FALSE"]], whNotHasRecTRUE, snTr,
                  pkgDTBase[["FALSE"]][[snFa]][whNotHasRecTRUE])
              if (!is.null(pkgDTBase[["FALSE"]][[depFa]]))
                set(pkgDTBase[["FALSE"]], whNotHasRecTRUE, depTr,
                  pkgDTBase[["FALSE"]][[depFa]][whNotHasRecTRUE])
            }
            # noSnTr <- which(is.na(pkgDTBase[["FALSE"]][[snTr]]))
            # set(pkgDTBase[["FALSE"]],  NULL, snTr, gsub("FALSE$", "TRUE", pkgDTBase[["FALSE"]][[snTr]]))
            # if (length(whHasRecursiveTRUE)) {
            #   set(pkgDTBase[["FALSE"]],  NULL, snTr,
            #       gsub("FALSE$", "TRUE", pkgDTBase[["FALSE"]][[snTr]]))
            # }
            # }
            #   len <- length(pkgDTBase[["FALSE"]][[depTr]][whHasRecursiveTRUE])
            # moveDeps <- FALSE
            # if (len < 16) moveDeps <- is.null(unlist(moveDeps))
            #
            # if (!moveDeps) {
            #   set(pkgDTBase[["FALSE"]], whHasRecursiveTRUE, depTr, pkgDTBase[["FALSE"]][[depTr]][whHasRecursiveTRUE])
            # } else {
            #   set(pkgDTBase[["FALSE"]], NULL, depTr, lapply(seq(NROW(pkgDTBase[["FALSE"]])), function(x) emptyDT()))
            # }

            isNULL <- sapply(pkgDTBase[["FALSE"]][[depTr]][whHasRecursiveTRUE], is.null)
            if (any(!isNULL)) {
              assignPkgDTdepsToSaveNames(pkgDTBase[["FALSE"]], recursive = TRUE) # ,
              # rowsToUpdate = whHasRecursiveTRUE[!isNULL])
              # if (!all(sapply(seq(NROW(pkgDT))[!is.na(pkgDT$snFALSE)], function(i)
              #   isTRUE(all.equal(pkgDT[[depTr]][[i]], get(pkgDT[[snTr]][i], envir = envPkgDepDeps()))))))
            }
            # }
          }
        } else {
          keep <- match(colnames(pkgDTBase[["FALSE"]]), c(depFa, caFa)) |> na.omit()
          setnames(pkgDTBase[["FALSE"]],
                   old = c(depFa, caFa)[keep],
                   new = c(depTr, caTr)[keep])

        }
      }
      pkgDT <- rbindlistNULL(pkgDTBase, fill = TRUE, use.names = TRUE)

      # parentVersionQueried <- gsub("^.+\\_+(.+)\\_\\_+(htt|Arch).+$", "\\1",
      #                         pkgDT$snTRUE)
      set(pkgDT, NULL, "parentVersionQueried", parentPackageVersion)

    }

  }
  pkgDT <- addDepthAndParentPkg(pkgDT, nam = parentPackage,
                                parentPackageVersion = parentPackageVersion, .depth)

  return(pkgDT)
}


#' The `packages` argument may have up to 4 pieces of information for GitHub
#' packages: name, repository, branch, version. For CRAN-alikes, it will only
#' be 2 pieces: name, version. There can also be an inequality or equality, if
#' there is a version.
#'
#' @param repos is used for `ap`.
#'
#' @details
#' If version is not supplied, it will take the local, installed version, if it
#' exists. Otherwise, it is assumed that the HEAD is desired.
#' The function will find it in the `ap` or on `github.com`. For github packages,
#' this is obviously a slow step, which can be accelerated if user supplies a sha
#' or a version e.g., getDeps("PredictiveEcology/LandR@development (==1.0.2)")
#' @inheritParams pkgDep
#' @param pkgDT A `pkgDT` object e.g., from `toPkgDT`
#' @param parentChain A character string representing the chain of parent
#'   packages that required this package, e.g., `"digest -> reproducible"`.
#'   Used to provide context in "not on CRAN" messages. Default `""`.
#' @return
#' A (named) vector of SaveNames, which is a concatenation of the 2 or 4 elements
#' above, plus the `which` and the `recursive`.
getDeps <- function(pkgDT, which, recursive, type = type, repos, libPaths, verbose, parentChain = "") {
  fillDefaults(pkgDep)

  for (tf in c(TRUE, FALSE))
    if (is.null(pkgDT[[cached(tf)]]))
      set(pkgDT, NULL, cached(tf), FALSE)
  set(pkgDT, which(pkgDT$Package %in% .basePkgs), cached(FALSE), TRUE)
  set(pkgDT, which(pkgDT$Package %in% .basePkgs), cached(TRUE), TRUE)

  if (getOption("Require.useCache", TRUE)) {
    pkgDT <- getFromCache(pkgDT, which, recursive)
  }

  if (any(!pkgDT[[cached(recursive)]] %in% TRUE) &&
    sum(!pkgDT[["Package"]][!pkgDT[[cached(recursive)]]] %in% .basePkgs)) {

    recursiveHere <- FALSE
    set(pkgDT, NULL, "ord", seq(NROW(pkgDT)))
    pkgDTCached <- splitKeepOrderAndDTIntegrity(pkgDT, pkgDT[[cached(recursiveHere)]])
    messageVerbose("Not in Cache: ", paste(unique(pkgDTCached[["FALSE"]]$Package), collapse = comma),
                   verbose = verbose, verboseLevel = 3)
    whichCatRecursive <- whichCatRecursive(which, recursiveHere)
    isGH <- !is.na(pkgDTCached[["FALSE"]]$githubPkgName)
    pkgDTNonGH <- pkgDTGH <- NULL
    if (!all(pkgDTCached[["FALSE"]]$Package %in% .basePkgs)) {
      if (any(!isGH)) {
        pkgDTNonGH <- getDepsNonGH(pkgDTCached[["FALSE"]][!isGH], repos, type = type, verbose,
                                   which = which, whichCatRecursive, libPaths = libPaths,
                                   parentChain = parentChain)
      }
      if (any(isGH)) {
        pkgDTGH <- getDepsGH(pkgDTCached[["FALSE"]][isGH], verbose, which = which, whichCatRecursive,
                             libPaths = libPaths)
      }
      pkgDTCached[["FALSE"]] <- rbindlistNULL(list(pkgDTNonGH, pkgDTGH), use.names = TRUE, fill = TRUE)
    }
    pkgDT <- rbindlist(pkgDTCached, fill = TRUE, use.names = TRUE)
    pkgDT <- pkgDT[order(pkgDT$ord)]
  }

  return(pkgDT)
}


pkgDepCRAN <- function(pkgDT, which, repos, type, libPaths, verbose, parentChain = "") {
  fillDefaults(pkgDep)

  num <- NROW(unique(pkgDT$Package)) # can have src and bin listed
  messageVerbose("  ", num, " ", singularPlural(c("package", "packages"), v = num),
                 " on CRAN", verbose = verbose, verboseLevel = 3)

  if (!is.data.table(pkgDT))
    pkgDT <- toPkgDT(pkgDT) |> parsePackageFullname()

  pkgDT <- # try(
    joinToAvailablePackages(pkgDT, repos, type, which, verbose)

  if (!isTRUE(getOption("Require.offlineMode") %in% TRUE)) { # if available.packages wasn't available, it turns offlineMode TRUE

    needsVersionCheck <- !is.na(pkgDT$versionSpec) # | !is.na(pkgDT$VersionOnRepos)
    set(pkgDT, NULL, "availableVersionOK", NA) # default

    if (any(needsVersionCheck)) {
      pkgDT <- availableVersionOK(pkgDT)

      # NOT SURE ABOUT THIS -- SHOULDN"T BE NECESSARY TO PICK THE availableVersionOKthisOne
      # pkgDTVerNums <- split(pkgDT, f = needsVersionCheck)
      # # setnames(pkgDTVerNums$`TRUE`, old = "Version", "VersionOnRepos")
      # pkgDTVerNums$`TRUE` <- availableVersionOK(pkgDTVerNums$`TRUE`)
      # # setnames(pkgDTVerNums$`TRUE`, old = "VersionOnRepos", "Version")
      # set(pkgDTVerNums$`TRUE`, NULL, "keep", seq(NROW(pkgDTVerNums$`TRUE`)))
      # dups <- duplicated(pkgDTVerNums$`TRUE`$Package)
      # pkgDTVerNums$`TRUE`[which(dups), keep := if (any(availableVersionOK)) .I[availableVersionOKthisOne][1] else .I, by = "Package"]
      # pkgDTVerNums$`TRUE` <- pkgDTVerNums$`TRUE`[na.omit(unique(pkgDTVerNums$`TRUE`$keep))]
      # set(pkgDTVerNums$`TRUE`, NULL, "keep", NULL) # remove "keep" column; no longer needed
      # pkgDT <- rbindlist(pkgDTVerNums, fill = TRUE)

      # , availableVersionOK := compareVersion2(Version, versionSpec, inequality)]
    } else {
      set(pkgDT, NULL, "availableVersionOK", TRUE)
    }

    inCurrentCRAN <- inCurrentCRAN(pkgDT, verbose)

    if (any(!inCurrentCRAN)) {
      set(pkgDT, which(!inCurrentCRAN), "repoLocation", .txtArchive)
      pkgDTList <- split(pkgDT, by = "repoLocation")
      dups <- duplicated(pkgDTList$Archive$Package) # b/c will hvae src and bin --> not needed any more
      pkgDTList$Archive <- pkgDTList$Archive[which(!dups)]
      num <- NROW(pkgDTList$Archive$Package)
      archDT <- pkgDTList$Archive
      chainSuffix <- if (nzchar(parentChain)) paste0(" (required by: ", parentChain, ")") else ""
      pkgMsgs <- paste0(archDT$packageFullName, chainSuffix)
      messageVerbose(paste(pkgMsgs, collapse = comma), " ",
                     "not on CRAN; checking CRAN archives ... ", verbose = verbose)

      pkgDTList <- getArchiveDESCRIPTION(pkgDTList, repos, which, libPaths = libPaths, verbose, purge = FALSE)
      wcr <- whichCatRecursive(which, recursive = FALSE)
      hadArchive <- !is.na(pkgDTList$Archive$VersionOnRepos)
      whHadArchive <- which(hadArchive)
      didntFindOnArchives <- is.na(pkgDTList[[.txtArchive]]$DESCFileFull)
      if (any(didntFindOnArchives)) {
        messageVerbose(red("   Did not find archives of: ",
                           paste(pkgDTList[[.txtArchive]]$packageFullName[didntFindOnArchives], collapse = comma),
                           "\n   --> Maybe version misspecified or were they installed locally?"))

      }

      messageVerbose("    Done evaluating archives!", verbose = verbose)
      pkgDT <- rbindlistNULL(pkgDTList, fill = TRUE, use.names = TRUE)
    }
  }

  pkgDT[]
}


#' @inheritParams Require
pkgDepGitHub <- function(pkgDT, which, includeBase = FALSE, libPaths, verbose = getOption("Require.verbose")) {

  pkg <- masterMainToHead(pkgDT$packageFullName)

  localVersionOK <- pkgDT$installedVersionOK
  if (is.null(localVersionOK)) {
    pkgDT <- installedVersionOKPrecise(pkgDT, libPaths = libPaths)
    localVersionOK <- pkgDT$installedVersionOK
  }

  if (any(!localVersionOK %in% TRUE)) {
    pkgDTNotLocal <- dlGitHubDESCRIPTION(pkgDT[!localVersionOK %in% TRUE], purge = FALSE)
  }
  if (any(localVersionOK %in% TRUE)) {
    # pkgDT[localVersionOK %in% TRUE, DESCFile := base::system.file("DESCRIPTION", package = Package), by = "Package"]
    pkgDT[localVersionOK %in% TRUE, DESCFile := localFiles, by = "Package"]

    if (exists("pkgDTNotLocal", inherits = FALSE)) {
      pkgDT <- rbindlist(list(pkgDT[localVersionOK %in% TRUE], pkgDTNotLocal), fill = TRUE, use.names = TRUE)
      setorderv(pkgDT, "ord")
    }

  } else {
    pkgDT <- pkgDTNotLocal
  }

  hasVersionNum <- grep(grepExtractPkgs, pkgDT$packageFullName) # pkgDT$hasVers # grep(grepExtractPkgs, pkgDT$packageFullName)
  isHEAD <- grep("(HEAD)", pkgDT$packageFullName)
  if (length(isHEAD)) {
    set(pkgDT, isHEAD, "availableVersionOK", TRUE)
    hasVersionNum <- setdiff(hasVersionNum, isHEAD)
  }
  set(pkgDT, NULL, "availableVersionOK", NA)
  if (length(hasVersionNum)) {
    if (!isTRUE(getOption("Require.offlineMode"))) {
      pkgDT[hasVersionNum, VersionOnRepos := DESCRIPTIONFileVersionV(DESCFile, purge = FALSE)]
      if (is.null(pkgDT$versionSpec)) {
        pkgDT[hasVersionNum, versionSpec := extractVersionNumber(packageFullName)]
        pkgDT[hasVersionNum, inequality := extractInequality(packageFullName)]
      }
      isHEAD <- pkgDT$versionSpec %in% "HEAD"
      if (any(isHEAD)) {
        pkgDT[which(isHEAD), availableVersionOK := TRUE]
        hasVersionNum <- hasVersionNum[which(!isHEAD)]
      }
      pkgDT[hasVersionNum, availableVersionOK := compareVersion2(VersionOnRepos, versionSpec, inequality)]
    }
  }

  pkgDepDTOuter <- updateWithRemotesNamespaceAddRepos2(pkgDT, which, purge = FALSE, includeBase, verbose = verbose)

  pkgDepDTOuter
}


getDepsNonGH <- function(pkgDT, repos, verbose, type, which,
                           whichCatRecursive, libPaths, doSave = TRUE, parentChain = "") {
  pkgDT <- toPkgDTFull(pkgDT)

  pkgDT <- pkgDepCRAN(pkgDT, which = which, repos = repos, type = type, libPaths = libPaths, verbose = verbose,
                      parentChain = parentChain)

  if (endsWith(whichCatRecursive, "FALSE")) { # this is joining the ap, so can only be recursive = FALSE
    if (isTRUE(doSave)) {
      # could be archived from CRAN
      onCRAN <- if (!is.null(pkgDT$VersionOnRepos)) !is.na(pkgDT$VersionOnRepos) else rep(FALSE, NROW(pkgDT))
      whOnCRAN <- which(onCRAN)
      if (any(onCRAN)) {
        pkgDT <- assignPkgDTtoSaveNames(pkgDT, rowsToUpdate = whOnCRAN, which, verbose, whichCatRecursive)
      }
    }
  }

  pkgDT[]
}

getDepsGH <- function(pkgDT, verbose, which, whichCatRecursive, libPaths, doSave = TRUE) {
  if (!is.data.table(pkgDT)) {
    pkgDT <- toPkgDTFull(pkgDT)
  }

  pkgDT <- installedVersionOKPrecise(pkgDT, libPaths = libPaths)
  pkgDT <- parsePackageFullname(pkgDT, sorted = FALSE) # this sorted previously; now no
  # pkgDT <- whichToInstall(pkgDT, install = TRUE)

  # if (isTRUE(any(!pkgDT$installedVersionOK) && any(duplicated(pkgDT[["Package"]])))) {
  #   # This is helpful if there are many branches of the same package, this can shorten the time,
  #   #   but also, if a package branch is defunct, another branch's min version requirements
  #   #   may make that defunct branch obsolete, so don't check it
  #   pkgDT <- trimRedundancies(pkgDT) #
  # }
  installedOK <- pkgDT$installedVersionOK
  installedNotOK <- !installedOK
  shas <- character(NROW(pkgDT))
  set(pkgDT, NULL, "shas", shas)
  areNULLs <- rep(TRUE, NROW(pkgDT))
  if (isTRUE(any(installedNotOK))) {
    versionInPkgEnv <-
      getVersionOptionPkgEnv(paste0(pkgDT$Account[installedNotOK], "_", pkgDT$Package[installedNotOK]),
                             verNum = pkgDT$versionSpec[installedNotOK],
                             inequ = pkgDT$inequality[installedNotOK])
    if (!is.null(versionInPkgEnv)) {
      areNULLs <- sapply(versionInPkgEnv, is.null)
    }
    if (any(areNULLs) || length(areNULLs) == 0) {
      installedNoOKAndNoPkgEnv <- installedNotOK[areNULLs]
      installedNoOKAndNoPkgEnvWh <- which(installedNoOKAndNoPkgEnv)
      for (i in 1:2) {
        brLocals <- if (i == 1) pkgDT$Branch[installedNoOKAndNoPkgEnv] else rep("main", length(installedNoOKAndNoPkgEnv))
        haveLocalSHAfull <- grepl("^[[:alnum:]]{40}$", pkgDT$installedSha) # full length
        haveLocalSHA <- haveLocalSHAfull & !installedNoOKAndNoPkgEnv # full length
        installedNoOKAndNoPkgEnv <- installedNoOKAndNoPkgEnv & !haveLocalSHA
        installedNoOKAndNoPkgEnvWh <- which(installedNoOKAndNoPkgEnv)
        if (length(installedNoOKAndNoPkgEnvWh)) {
          shaOuts <- try( # This try is for "no internet"
            Map(
              repo = pkgDT$Repo[installedNoOKAndNoPkgEnvWh],
              acct = pkgDT$Account[installedNoOKAndNoPkgEnvWh],
              br = pkgDT$Branch[installedNoOKAndNoPkgEnvWh],
              verbose = verbose,
              getSHAfromGitHubMemoise
            ), silent = TRUE
          )
          if (is(shaOuts, "try-error")) {
            if (any(grepl(messageCantFind("|", "|", "|"), shaOuts)))
              stop(shaOuts)
            if (any(grepl("Could not resolve host", shaOuts))) {
              shaOuts <- ""
            }
          }
          individualFails <- vapply(shaOuts, is, "try-error", FUN.VALUE = logical(1))
          if (any(individualFails)) {
            shaOuts[individualFails] <- Map(sh = shaOuts[individualFails], function(sh) "")
          }
          pkgDT[installedNoOKAndNoPkgEnvWh, shas := unlist(shaOuts)]
        }
        if (sum(installedNoOKAndNoPkgEnv %in% FALSE)) {
          pkgDT[which(installedNoOKAndNoPkgEnv %in% FALSE), shas := installedSha]
        }
        needVersion <- needVersions(pkgDT)
        # if (any(needVersion)) {
        descs <- dlGitHubDESCRIPTION(pkgDT)#$packageFullName)
        pkgDT[descs, descFiles := DESCFile,  on = "packageFullName"]
        wh <- which(!is.na(pkgDT$descFiles))
        if (isTRUE(getOption("Require.offlineMode"))) {
          fe <- file.exists(pkgDT$descFiles)
          wh <- wh[fe]
        }
        pkgDT[wh, Version := DESCRIPTIONFileVersionV(descFiles)]
        possPkgUpdate <- DESCRIPTIONFileOtherV(pkgDT$descFiles[wh], other = "Package")
        correctPkgName <- possPkgUpdate == pkgDT$Package[wh]
        if (any(correctPkgName %in% FALSE)) {
          set(pkgDT, wh, "Package", possPkgUpdate)
          set(pkgDT, wh, "githubPkgName", possPkgUpdate)
        }
        # }
        break

      }
    }
  }

  dups <- duplicated(pkgDT$packageFullName)
  if (any(dups))
    pkgDT <- pkgDT[!dups]

  # next changes order
  out <- pkgDepGitHub(pkgDT = pkgDT, which = which,
                      includeBase = FALSE, libPaths = libPaths, verbose = verbose)
  rec <- FALSE # this function is only one time through

  val <- unname(out)
  if (length(val) == 0)
    val <- lapply(seq_len(NROW(pkgDT)), function(x)
      data.table(Package = character(), which = character(), packageFullName = character()))
  set(pkgDT, NULL, deps(FALSE), val)

  # rec <- recursiveType(whichCatRecursive)
  snHere <- sn(rec)
  set(pkgDT, NULL, snHere, saveNameConcat(pkgDT, whichCatRecursive))

  if (isTRUE(doSave)) {
    pkgDT <- assignPkgDTdepsToSaveNames(pkgDT = pkgDT, recursive = rec)
  }

  pkgDT[]
}

saveNameConcat <- function(pkgDT, whichCatRecursive = NULL, versionCol = c("Version", "versionSpec"), shasCol = "shas") {
  p <- pkgDT

  if (is.null(p$repoLocation)) {
    isGH <- isGitHub(p$packageFullName)
  } else {
    isGH <- p$repoLocation %in% .txtGitHub
  }
  if (any(isGH)) {
    p <- toPkgDTFull(p)
  }
  if (length(versionCol) > 1)
    versionCol <- intersect(versionCol, colnames(pkgDT))[1]
  if (is.null(pkgDT[[shasCol]]))
    shasCol <- "Branch"

  out <- with(p, ifelse(isGH,
                        saveNameConcat2(argsCh = saveNameOrderGH(versionCol, shasCol)),
                        saveNameConcat2(argsCh = saveNameOrderNonGH(versionCol))))
                        # saveNameConcatGH(Package, get(versionCol), Account, Repo, Branch, get(shasCol)),
                        # saveNameConcatNonGH(Package, get(versionCol))))

  isCRAN <- p$repoLocation %in% "CRAN"
  val <- p$repoLocation
  if (any(isCRAN))
    val[isCRAN] <- gsub(":|/", "_", p$Repository[isCRAN])
  paste(out, val, whichCatRecursive, sep = sepForSaveNames)
}

saveNameConcat2 <- function(argsCh, envir = parent.frame()) {
  mget(argsCh, envir = envir) |>
    append(list(paste, sep = sepForSaveNames)) |>
    do.call(what = Map) |>
    unlist() |>
    unname()
  # unname(unlist(do.call(Map, append(mget(argsCh, envir = envir), list(paste, sep = sepForSaveNames)))))
}


saveNameOrderNonGH <- function(Version = "Version") {
  c("Package", Version)
}
saveNameOrderGH <- function(Version, shas = "shas") {
  c(saveNameOrderNonGH(Version), "Account", "Repo", "Branch", shas)
}

sepForSaveNames <- "__"
sepForWhich <- "_"

# saveNamesLabel <- function(pkgDT) {
#   if (!is.null(pkgDT$Account)) {
#     isGH <- !is.na(pkgDT$Account)
#   } else {
#     isGH <- rep(FALSE, NROW(pkgDT))
#   }
#   set(pkgDT, NULL, "label", pkgDT$Package)
#   set(pkgDT, which(isGH), "label",
#       paste0(pkgDT$Account[isGH], "/",
#              pkgDT$Repo[isGH], "@",
#              pkgDT$Branch[isGH]))
#
#   paste0(pkgDT$label, " (==", pkgDT$Version, ")")
# }


needVersions <- function(pkgDT) {
  if ("Version" %in% colnames(pkgDT)) {
    needVersions <- is.na(pkgDT$Version)
  } else {
    needVersions <- rep(TRUE, NROW(pkgDT))
  }
  needVersions
}


updateWithRemotesNamespaceAddRepos2 <- function(pkgDT, which, purge, includeBase, verbose) {
  if (!"shas" %in% colnames(pkgDT))
    set(pkgDT, NULL, "shas", pkgDT$Branch)
  fe <- seq_len(NROW(pkgDT))
  if (isTRUE(getOption("Require.offlineMode"))) {
    fe <- which(file.exists(pkgDT$DESCFile))
  }


  out <- pkgDT[fe, list(# packageFullName = packageFullName,
                      lis = {
      allDeps <- DESCRIPTIONFileDeps(DESCFile, which = c("Depends", "Imports", "Suggests", "LinkingTo"),
                                     purge = purge, keepSeparate = TRUE)
      needed <- allDeps[which]
      notNeeded <- allDeps[setdiff(names(allDeps), which)]
      neededAdditionalRepos <- DESCRIPTIONFileOtherV(DESCFile, other = "Additional_repositories")
      neededRemotes <- DESCRIPTIONFileDeps(DESCFile, which = "Remotes", purge = purge)
      pfn <- gsub("(@).+( *)", paste0("\\1", shas, "\\2"), packageFullName)
    # Change branch to use sha
    uwrnar(needed = needed, notNeeded = notNeeded, neededRemotes, installedVersionOK, Package,
           pfn, neededAdditionalRepos, shas = shas, includeBase, localFiles = localFiles, verbose)
  }), by = "packageFullName"]

  out1 <- out$lis
  names(out1) <- out$packageFullName
  return(out1)
}


uwrnar <- function(needed, notNeeded, neededRemotes, installedVersionOK, Package,
                   # Repo, Account, Branch, hasSubFolder,
                   packageFullName, neededAdditionalRepos, shas,
                   includeBase, localFiles, verbose) {
  neededOrig <- needed
  if (!is.null(unlist(neededOrig))) {
    needed <- unname(unlist(needed))
    neededRemotesName <- extractPkgName(neededRemotes)
    neededName <- extractPkgName(needed)
    notNeededName <- c(extractPkgName(unname(unlist(notNeeded))))

    needSomeRemotesNames <- setdiff(neededRemotesName, notNeededName)
    needSomeRemotes <- neededName %in% needSomeRemotesNames
    notNeededRemotes <- setdiff(neededRemotesName, neededName)
    if (length(notNeededName)) {
      somehowMissing <- setdiff(neededRemotesName, c(notNeededRemotes, needSomeRemotes))
      # needSomeRemotes <- neededName %in% neededRemotesName
      dontMatch <- neededRemotes[match(somehowMissing, neededRemotesName)]
      if (length(dontMatch)) { # These either are missing from Depends/Imports/Suggests but are in Remotes
        #  or else the package name doesn't match the GitHub repo name e.g., BioSIM = RNCan/BioSimClient_R
        #  Need to try to figure out which it is for the dontMatch
        Packages <- Map(packageFullName = trimVersionNumber(dontMatch), function(packageFullName) {
          descFiles <- dlGitHubDESCRIPTION(packageFullName)
          DESCRIPTIONFileOtherV(descFiles[["DESCFile"]], "Package")
        })
        whPackages <- match(extractPkgName(needed), unname(unlist(Packages))) |> na.omit()
        Packages <- Packages[whPackages]

        RepoNotPkgName <- !mapply(pack = Packages, nam = names(Packages), function(pack, nam) {
          extractPkgGitHub(nam) == pack
        })

        if (any(RepoNotPkgName)) {
          addToNeededName <- neededName %in% unname(Packages[RepoNotPkgName])
          needSomeRemotes[addToNeededName] <- TRUE
          neededName <- unique(c(neededName, extractPkgName(names(Packages)[RepoNotPkgName])))
        }


      }
    }
    if (any(needSomeRemotes)) {
      hasVersionNum <- grep(grepExtractPkgs, needed[needSomeRemotes])
      if (length(hasVersionNum)) {
        neededInRemotesWVersion <- needed[needSomeRemotes][hasVersionNum]
        vn <- extractVersionNumber(neededInRemotesWVersion)
        ineq <- extractInequality(neededInRemotesWVersion)
        neededPkgsInRemotes <-
          extractPkgName(neededInRemotesWVersion)
        inequWVN <- paste0(" (", ineq, " ", vn, ")")
        remotes <- neededRemotes[neededRemotesName %in% neededName]
        whNeedVN <-
          match(neededPkgsInRemotes, extractPkgName(remotes))
        remotesWVN <- remotes[whNeedVN]
        remotesWVN <- paste0(remotesWVN, inequWVN)
        remotesWoVN <-
          if (length(neededRemotes) != length(whNeedVN)) {
            remotes[-whNeedVN]
          } else {
            NULL
          }
        remotesAll <- c(remotesWVN, remotesWoVN)
      } else {
        remotesAll <- neededRemotes[neededRemotesName %in% neededName]
      }
      needed <- c(needed[!needSomeRemotes], remotesAll)
    }

    # Check NAMESPACE too -- because imperfect DESCRIPTION files
    if (isTRUE(installedVersionOK)) {
      # namespaceFile <- base::system.file("NAMESPACE", package = Package)
      namespaceFile <- file.path(dirname(localFiles), "NAMESPACE")
    } else {
      namespaceFile <-
        dlGitHubNamespace(packageFullName)[["NAMESPACE"]]
    }
    if (is.null(namespaceFile)) {
      depsFromNamespace <- NULL
    } else {
      rr <- readLines(namespaceFile)
      depsFromNamespace <- gsub(", except.*(\\))$", "\\1", rr)
      depsFromNamespace <-
        unique(gsub(
          "^import.*\\((.+)\\,.*$",
          "\\1",
          grep("^import", depsFromNamespace, value = TRUE)
        ))
      depsFromNamespace <-
        unique(gsub("^import\\((.+)\\)", "\\1", depsFromNamespace))
      depsFromNamespace <- gsub(",.*", "", depsFromNamespace)
      depsFromNamespace <- gsub("\\\"", "", depsFromNamespace)



    }
    bp <- if (isTRUE(includeBase)) {
      NULL
    } else {
      .basePkgs
    }
    # needed could have version number specification
    neededReally <- union(needed, depsFromNamespace)
    neededReally <- neededReally[!extractPkgName(neededReally) %in% bp]
    neededReally <- rmRifInPackageCol(neededReally)

    pkgDepDT <- toPkgDepDT(neededReally, needed,
                           Package, verbose)

    if (exists("Packages", inherits = FALSE)) {
      whOverride <- match(names(Packages)[RepoNotPkgName], pkgDepDT$packageFullName)
      if (length(whOverride))
        set(pkgDepDT, whOverride, "Package", unname(unlist(Packages[RepoNotPkgName])))
    }
    if (!is.na(neededAdditionalRepos))
      pkgDepDT[, Additional_repositories := neededAdditionalRepos]

    neededWithWhich <- unlist(neededOrig, use.names = TRUE)
    neededWithWhich <- list(Package = extractPkgName(unname(neededWithWhich)),
                            # packageFullName = neededWithWhich,
                            which = names(neededWithWhich)) |>
      setDT()
    set(neededWithWhich, NULL, "which", gsub("[[:digit:]]", "", neededWithWhich[["which"]]))
    pkgDepDT <- neededWithWhich[pkgDepDT, on = "Package"]
  } else {
    pkgDepDT <- toPkgDepDT(NULL, needed, Package, verbose)
    set(pkgDepDT, NULL, "which", character())
  }

  list(pkgDepDT)
}

fillDefaults <- function(fillFromFn, envir = parent.frame()) {
  i <- 0
  sf <- sys.frames()
  len <- length(sf)
  for (en in rev(sf)) {
    i  <- i + 1
    if (identical(en, envir))
      break
  }
  whFrame <- len - i + 1
  # whFrame <- which(sapply(sys.frames(), function(en) identical(en, envir)))
  mc <- match.call(sys.function(whFrame), call = sys.call(whFrame))
  used <- names(mc)[-1]
  forms <- formalArgs(eval(as.list(mc[1])[[1]]))
  # miss <- setdiff(forms, used)
  miss <- forms[!forms %in% used]
  for (m in miss) {
    defVal <- eval(formals(fillFromFn)[[m]])
    assign(m, defVal, envir = parent.frame())
  }
}




#' @importFrom utils download.file untar
#' @include messages.R
getArchiveDESCRIPTION <- function(pkgDTList, repos, purge = FALSE, which, libPaths,
                                  verbose = getOption("Require.verbose")) {

  tmpdir <- tempdir3() # faster than tempdir2
  on.exit({
    unlink(tmpdir, recursive = TRUE)
  }, add = TRUE)
  for (attempt in 1:2) {
    pkgDTList$Archive <- identifyLocalFiles(pkgDTList$Archive, repos = repos, purge = purge,
                                            libPaths = libPaths, verbose = verbose)
    haveLocal2 <- pkgDTList$Archive[["haveLocal"]] %in% .txtLocal
    if (any(haveLocal2))
      pkgDTList$Archive[, PackageUrl := file.path(Package, basename(localFile))]
    if (!all(haveLocal2)) {
      # downloadArchive takes a list with an element called .txtArchive so can't split on .txtArchive here
      # noLocals <- pkgDTList$Archive$haveLocal2 %in% TRUE
      txtArchiveHaveLocal <- "ArchiveHaveLocal"
      if (any(haveLocal2)) {
        pkgDTList[[txtArchiveHaveLocal]] <- pkgDTList$Archive[which(haveLocal2), ]
      }
      pkgDTList$Archive <- pkgDTList$Archive[which(!haveLocal2), ]
      if (!is.null(pkgDTList$Archive)) {
        pkgDTList <- downloadArchive(pkgDTList, repos = repos, purge = purge,
                                     tmpdir = tmpdir, verbose = verbose)
      }
      if (!is.null(pkgDTList[[txtArchiveHaveLocal]])) {
        pkgDTList$Archive <-
          rbindlist(pkgDTList[c(.txtArchive, txtArchiveHaveLocal)],
                    fill = TRUE, use.names = TRUE)
        pkgDTList[[txtArchiveHaveLocal]] <- NULL
      }
    }

    if (any(!is.na(pkgDTList$Archive$PackageUrl))) {
      if (is.null(pkgDTList$Archive[["DESCFileFull"]])) # add empty here; allows for retrying above
        set(pkgDTList$Archive, NULL, "DESCFileFull", "")
      pkgDTList$Archive[!is.na(PackageUrl) & !nzchar(DESCFileFull), DESCFileFull := {
        .DESCFileFull(PackageUrl, verbose, Repository, Package, tmpdir)
      }, by = "packageFullName"]

      # The local file in Cache can sometimes be corrupt; it gets deleted in `.DESCFileFull`
      fails <- (pkgDTList$Archive[!is.na(PackageUrl)][, "DESCFileFull"] == 0) %in% TRUE
      if (any(fails)) {
        set(pkgDTList$Archive, which(fails),
            c("haveLocal", "localFile", "installFrom", "DESCFileFull"),
            list(.txtNoLocal, "", NA, ""))
        next
      }


      gotDESC <- !is.na(pkgDTList$Archive$DESCFileFull) & nzchar(pkgDTList$Archive$DESCFileFull)
      whGotDESC <- which(gotDESC)

      VoR <- DESCRIPTIONFileOtherV(pkgDTList$Archive[whGotDESC]$DESCFileFull, "Version")
      pkgDTList$Archive[whGotDESC, VersionOnRepos := VoR]
      deps <- DESCRIPTIONFileDepsV(
        pkgDTList$Archive[whGotDESC]$DESCFileFull,
        which = which, keepSeparate = TRUE)
      names(deps) <- pkgDTList$Archive$packageFullName[whGotDESC]
      deps <- lapply(deps, function(x) {
        unlist(lapply(x, function(y) paste(y, collapse = comma)))
      })
      deps <- do.call(rbind, deps)
      # deps <- invertList(deps)
      # deps <- as.data.table(deps)
      for (co in colnames(deps))
        set(pkgDTList$Archive, whGotDESC, co, deps[, co])
    }
  }
  pkgDTList
}


installed.packagesDeps <- function(ip, libPaths, which) {
  if (missing(ip))
    ip <- .installed.pkgs(lib.loc = libPaths, which = which)

  if (!is.data.table(ip))
    ip <- as.data.table(ip)

  ip[, deps := do.call(paste, append(.SD, list(sep = comma))), .SDcols = which]

  # remove trailing and initial commas
  set(ip, NULL, "deps", gsub("(, )+$", "", ip$deps))
  set(ip, NULL, "deps", gsub("^(, )+", "", ip$deps))
  # remove middle empty commas
  set(ip, NULL, "deps", gsub(", ,", ",", ip$deps))
  # remove empty, but no space
  set(ip, NULL, "deps", gsub("(,,)+", ",", ip$deps))
}

depsWithCommasToVector <- function(packageFullName, depsWithCommas) {
  # packageFullName is used to name the list
  ll <- strsplit(depsWithCommas, split = "(, {0,1})|(,\n)")
  names(ll) <- packageFullName
  ll <- lapply(ll, grep, pattern = .grepR, value = TRUE, invert = TRUE)
  ll <- lapply(ll, function(x) x[!is.na(x)])
  ll
}


cleanPkgs <- function(packageFullName, colsToClean = c("packageFullName", "parentPackage")) {
  if (is(packageFullName, "list")) {
    if (is.data.table(packageFullName[[1]])) {
      deps <- Map(pfn = packageFullName, function(pfn) {
        for (pk in colsToClean)
          set(pfn, NULL, pk, cleanPkgs(pfn[[pk]]))
        pfn
      })
      return(deps)
    } else {
      return(cleanPkgs(packageFullName))
    }
  }
  pkgsCleaned <- gsub(.grepTooManySpaces, " ", packageFullName)
  pkgsCleaned <- gsub(.grepTabCR, "", pkgsCleaned)
  pkgsCleaned <- gsub("(\\S)\\(", "\\1 (", pkgsCleaned) # missing space before (
  gsub("(>|=|<)([[:digit:]])", "\\1 \\2", pkgsCleaned)
}


assignPkgDTtoSaveNames <- function(pkgDT, rowsToUpdate = seq_len(NROW(pkgDT)), which, verbose, whichCatRecursive,
                                   versionCol = "VersionOnRepos") {
  if (NROW(pkgDT[rowsToUpdate])) {
    deps3 <- depsWithCommasToPkgDT(pkgDT[rowsToUpdate], which)
    depsAll <- Map(toPkgDepDT, deps3, verbose = verbose)

    actualSN <- saveNameConcat(pkgDT[rowsToUpdate], whichCatRecursive, versionCol = versionCol)

    recursive <- recursiveType(whichCatRecursive)
    set(pkgDT, rowsToUpdate, sn(recursive), actualSN)
    set(pkgDT, rowsToUpdate, deps(recursive), unname(depsAll))

    # "cachedFALSE is FALSE, but the depsFALSE is already there!"

    pkgDT <- assignPkgDTdepsToSaveNames(pkgDT, rowsToUpdate, recursive = recursive)
  }
  pkgDT
}

sn <- function(recursive)
  .suffix("sn", recursive)

deps <- function(recursive)
  .suffix("deps", recursive)

cached <- function(recursive)
  .suffix("cached", recursive)

recursiveType <- function(x)
  as.logical(strsplit(x, split = "Recursive")[[1]][2])

.suffix <- function(x, suff)
  paste0(x, suff)

whichCatRecursive <- function(which, recursive) {
  whichCat <- paste(sort(which), collapse = sepForWhich)

  if (is.logical(recursive))
    recursive <- as.character(recursive)
  if (!startsWith(recursive, "Recursive"))
    recursive <- paste0("Recursive", recursive)
  paste(whichCat, recursive, sep = "")[1]
}


depsListToPkgDTWithWhichCol <- function(deps2) {
  deps3 <- lapply(deps2, function(dep) {
    dep1 <- unlist(dep, use.names = TRUE)
    dep2 <- list(Package = extractPkgName(unname(dep1)),
                 packageFullName = dep1,
                 which = names(dep1)) |>
      setDT()
    set(dep2, NULL, "which", gsub("[[:digit:]]", "", dep2[["which"]]))
    dep2
  })
}


#' Join a data.table with a `Package` column to `available.packages`
#'
#' Will join `available.packages()` with `pkgDT`, if `pkgDT` does not already have
#' a column named `Depends`, which would be an indicator that this had already
#' happened.
#' @inheritParams getDeps
#'
#' @return
#' The returned `data.table` will have most of the columns from
#' `available.packages` appended to the `pkgDT`, including `Depends`, `Imports`,
#' `Suggests`. It will change the column name that is normally
#'   returned from `available.packages` as `Version` to `VersionOnRepos`.
joinToAvailablePackages <- function(pkgDT, repos, type, which, verbose) {

  if (is.null(pkgDT[["Depends"]])) {
    if (any(!pkgDT$Package %in% .basePkgs)) {

      ap <- available.packagesCached(repos, purge = FALSE, verbose = verbose, type = type)
      # set(ap, NULL, "packageFullName", paste0(ap$Package, " (==", ap$Version, ")"))
      keep <- c("Package", "Version", "Repository", which)
      ap <- ap[ap$Package %in% pkgDT$Package][, ..keep]
      setnames(ap, old = "Version", new = "VersionOnRepos")
      cn <- setdiff(colnames(ap), c("Package", "packageFullName", which))
      onlyKeep <- c(setdiff(colnames(pkgDT), cn))

      # DON"T JOIN ON packageFullName because we want to know "what is on CRAN", not whether it is exact
      dups <- duplicated(ap$Package)
      pkgDT <- ap[!dups][pkgDT[, ..onlyKeep], on = c("Package")]

      # pkgDT <- ap[pkgDT[, ..onlyKeep], on = c("Package")] # ap[pkgDT, on = "Package"]
      # # typeIsDiffVers <- pkgDT[, .N, by = "Package"]
      # dups <- duplicated(pkgDT, by = c("Package")) # prev included "VersionOnRepos"; but this was always a diff version by "type"
      # if (any(dups))
      #   pkgDT <- pkgDT[!dups]
    }
  }
  pkgDT
}


rmRifInPackageCol <- function(pkgDT) {
  if (is(pkgDT, "list")) {
    out <- lapply(pkgDT, rmRifInPackageCol)
    return(out)
  }
  if (is.data.table(pkgDT)) {
    hasRasDep <- pkgDT[["Package"]] == "R"
    if (isTRUE(any(hasRasDep)))
      pkgDT <- pkgDT[-which(hasRasDep)]
    out <- pkgDT[]
  } else {
    out <- grep(.grepR, pkgDT, value = TRUE, invert = TRUE)
  }
  out
}



inCurrentCRAN <- function(pkgDT, verbose) {
  inCurrentCRAN <- !is.na(pkgDT$VersionOnRepos[!pkgDT$Package %in% .basePkgs]) # this is "removed and gone"
  if (isTRUE(any(!pkgDT$availableVersionOK %in% TRUE)))
    inCurrentCRAN <- inCurrentCRAN & pkgDT$availableVersionOK

  if (sum(inCurrentCRAN)) {
    if (any(inCurrentCRAN)) {
      num <- NROW(unique(pkgDT$Package[inCurrentCRAN]))
      messageVerbose("  Done for ", num, " ", singularPlural(c("package", "packages"), v = num),
                     " currently on CRAN!", verbose = verbose, verboseLevel = 3)
    }
  }
  inCurrentCRAN
}

assignPkgDTdepsToSaveNames <- function(pkgDT, rowsToUpdate = seq(NROW(pkgDT)), recursive) {
  sn <- sn(recursive)
  deps <- deps(recursive)

  deps1 <- pkgDT[[deps]][rowsToUpdate]
  names(deps1) <- pkgDT[[sn]][rowsToUpdate]
  isDT <- unlist(lapply(deps1, is.data.table))
  if (!all(isDT)) {
    deps1[which(!isDT)] <- lapply(deps1[which(!isDT)], rbindlistRecursive)
  }
  list2env(deps1, envir = envPkgDepDeps())
  set(pkgDT, rowsToUpdate, cached(recursive), TRUE)

  pkgDT[]
}



addDepthAndParentPkg <- function(deps, nam, parentPackageVersion, .depth) {
  if (!is.null(deps))
    if (!is.null(nam)) {
      if (is(deps, "list")) {
        return(Map(deps = deps, nam = names(deps), addDepthAndParentPkg, .depth = .depth))
      }
      setDT(deps) # sometimes esp. on Mac:
      # Error in `set(deps, NULL, "parentPackage", nam)`: This data.table has either
      #   been loaded from disk (e.g. using readRDS()/load()) or constructed manually
      #   (e.g. using structure()). Please run setDT() or setalloccol() on it first
      #   (to pre-allocate space for new columns) before assigning by reference to it.

      set(deps, NULL, "parentPackage", nam)
      set(deps, NULL, "parentPackageVersion", parentPackageVersion)
      deps <- rmRifInPackageCol(deps)
      set(deps, NULL, ".depth", .depth)
    }
  deps
}


reorderTo <- function(deps, packages) {
  deps[na.omit(match(packages, names(deps)))]
}

depsWithCommasToPkgDT <- function(pkgDT, which) {
  packageFullName <- pkgDT$packageFullName
  deps1 <- list()
  for (cn in which)
    deps1[[cn]] <- depsWithCommasToVector(packageFullName, pkgDT[[cn]])
  if (sum(lengths(deps1))) {
    deps2 <- invertList(deps1)
    deps1 <- depsListToPkgDTWithWhichCol(deps2)
  }
  deps1

}


#' Recursive function to remove `.basePkgs`
#'
#' @param deps Either a list of dependencies, a data.table of dependencies with
#'   a column `Package` or a vector of dependencies.
#' @param includeBase Logical. If `FALSE`, the default, then base packages will
#'   be removed.
rmBase <- function(includeBase = formals(pkgDep)[["includeBase"]], deps) {
  if (includeBase %in% FALSE) {
    if (is(deps, "list")) {
      deps <- lapply(deps, rmBase, includeBase = includeBase)
    } else {
      if (NROW(deps)) {
        if (is(deps, "data.table")) {
          deps <- deps[!deps$Package %in% .basePkgs]

        } else {
          deps <- deps[!extractPkgName(deps) %in% .basePkgs]
        }
      }
    }
  }
  return(deps)
}

addSelf <- function(includeSelf, deps, self) {
  if (includeSelf %in% TRUE) {
    if (is(deps, "list")) {
      deps <- Map(dep1 = deps, self = self, function(dep1, self) {
        addSelf(includeSelf, dep1, self)
      })
    } else {
      deps <- rbindlist(list(toPkgDepDT(self), deps), fill = TRUE, use.names = TRUE)
    }
  }
  return(deps)
}


toPkgDTFull <- function(pkgDT) {
  if (!is.data.table(pkgDT)) {
    packagesNames <- names(pkgDT)
    pkgDT <- toPkgDT(pkgDT)
  }
  if (is.null(pkgDT$versionSpec))
    pkgDT <- parsePackageFullname(pkgDT, sorted = FALSE)
  if (is.null(pkgDT$Account))
    pkgDT <- parseGitHub(pkgDT)

  if (exists("packagesNames", inherits = FALSE))
    pkgDT <- updatePackagesWithNames(pkgDT, packagesNames)

  pkgDT <- checkHEAD(pkgDT)


  # set(pkgDT, NULL, "topLevelOrder", seq(NROW(pkgDT)))

  pkgDT
}

#' `split` for a data.table that keeps integrity of a column of lists of data.table objects
#'
#' `data.table::split` does 2 bad things:
#'  1. reorders if using `f`
#'  2. destroys the integrity of a column that is a list of data.tables, when using `by`
#' So, to keep order, need `by`, but to keep integrity, need `f`. This function
#' @inheritParams getDeps
#' @param splitOn Character vector passed to `data.table::split(..., f = splitOn)`
#' @return
#' A list of `data.table` objects of `length(unique(splitOn))`.
splitKeepOrderAndDTIntegrity <- function(pkgDT, splitOn) {
  # keeps order and integrity.
  # split reorders!! careful
  # The data.table "by" doesn't work -- the list of data.tables in the deps*** column doesn't work
  # pkgDTHaveDeps <- pkgDT[whHasDeps]
  # pkgDTHaveDeps <- split(pkgDTHaveDeps, by = "packageFullName", sorted = FALSE)
  # splitOn -- using the split.data.table with `f` treats as a factor, which can get reordered inadvertently

  # splitOn <- pkgDT$packageFullName[whHasDeps]
  splitOn <- factor(splitOn, levels = unique(splitOn)) # must keep order
  pkgDTList <- split(pkgDT, f = splitOn)

  # Next line is about dealing with this
  # Internal error: DT passed to assign has not been allocated enough column slots
  pkgDTList <- lapply(pkgDTList, data.table::alloc.col)
  pkgDTList
  # pkgDTHaveDeps <- split(pkgDT[whHasDeps], f = splitOn)
}

getFromCache <- function(pkgDT, which, recursive) {

  curCache <- names(envPkgDepDeps())
  # must be correct which & recursive too
  curCache <- grep(whichCatRecursive(which, ""), curCache, value = TRUE)
  maybeHaveCache <- Map(nam = pkgDT$packageFullName, sw = paste0(pkgDT$Package, sepForSaveNames),
                        function(nam, sw) which(startsWith(prefix = sw, curCache)))
  maybeHaveCacheCurCache <- unlist(maybeHaveCache)
  maybeHaveCacheDT <- list(Package = rep(pkgDT$Package, lengths(maybeHaveCache)),
                             packageFullName = rep(names(maybeHaveCache), lengths(maybeHaveCache)),
                           rowNum = maybeHaveCacheCurCache) |> setDT()

  if (any(maybeHaveCacheDT$packageFullName %in% pkgDT$packageFullName)) {
    lst <- sapply(maybeHaveCacheDT, is.list)
    keep <- setdiff(colnames(maybeHaveCacheDT), names(lst)[which(lst)])
    dups <- duplicated(maybeHaveCacheDT[, ..keep])
    if (any(dups)) {
      maybeHaveCacheDT <- maybeHaveCacheDT[which(!dups)]
    }

    keep <- intersect(c("Package", "packageFullName", deps(FALSE)), colnames(pkgDT))
    keepShort <- keep[1:2]
    dups <- duplicated(pkgDT[, ..keepShort])
    pkgDT2 <- if (any(dups)) {
      pkgDT[which(!dups)]
    } else {
      pkgDT
    }

    maybeHaveCacheDT <- pkgDT2[, ..keep][maybeHaveCacheDT, on = c("Package", "packageFullName")]

    if (recursive %in% TRUE) {
      pkgDT <- getDepsFromCache(pkgDT, maybeHaveCacheDT, recursive = TRUE, curCache)
    }
    pkgDT <- getDepsFromCache(pkgDT, maybeHaveCacheDT, recursive = FALSE, curCache)

  }
  pkgDT
}


getDepsFromCache <- function(pkgDT, maybeHaveCacheDT, recursive, curCache) {
  # if (!allNotNull(pkgDT[[cached(TRUE)]])) {

  # if (recursive %in% FALSE && !allNotNull(pkgDT[[cached(TRUE)]])) {
  if (is.null(pkgDT[[cached(recursive)]])) {
    alreadyCached <- rep(FALSE, NROW(pkgDT))
  } else {
    alreadyCached <- pkgDT[[cached(recursive)]] %in% TRUE
  }

  if (any(alreadyCached)) {
    toRm <- maybeHaveCacheDT$packageFullName %in% pkgDT$packageFullName[alreadyCached]
    maybeHaveCacheDT <- maybeHaveCacheDT[toRm]
  }

  haveInCache <- endsWith(curCache[maybeHaveCacheDT$rowNum], as.character(recursive))

  if (isTRUE(any(haveInCache))) {
    isRecursiveTxt <- paste0("isRecursive", recursive)

    set(maybeHaveCacheDT, NULL, isRecursiveTxt, haveInCache)
    maybeHaveCacheDTRec <- maybeHaveCacheDT[which(haveInCache)]
    set(maybeHaveCacheDTRec, NULL, "curCache", curCache[maybeHaveCacheDTRec$rowNum])

    # quick for CRAN

      getFromCurCachePoss <- grepl(paste0(sepForSaveNames, "(CRAN)", sepForSaveNames, paste0(".+", recursive, "$")),
                                   maybeHaveCacheDTRec$curCache)
      # slow for GitHub
      if (any(getFromCurCachePoss %in% FALSE)) {
        maybeHaveCacheDTRec <- toPkgDTFull(maybeHaveCacheDTRec)
        if (any(!is.na(maybeHaveCacheDTRec$githubPkgName))) {
          colsForComparison <- intersect(c("Package", "Account", "Repo", "Branch"),
                                         saveNameOrderGH(Version = "Version"))
          colsForComparison <- c(colsForComparison, "repoLocation")
          wh <- maybeHaveCacheDTRec[
            , list(wh = all(greplV(unname(unlist(mget(colsForComparison))), curCache)))
            , by = seq(NROW(maybeHaveCacheDTRec))]$wh
          # wh <- with(maybeHaveCacheDTRec[, ..colsForComparison], {
          #   greplV(c(Package, Account, Repo, Branch, repoLocation),
          #                                        maybeHaveCacheDTRec$curCache)
          #   })
          getFromCurCachePoss <- getFromCurCachePoss | wh %in% TRUE
        }
      }

      versions <- sapply(strsplit(maybeHaveCacheDTRec$curCache, sepForSaveNames), function(x) x[2])
      set(maybeHaveCacheDTRec, NULL, "version", versions)
      haveCache <- getFromCurCachePoss
      CRANvOKbcNeedLatest <- is.null(maybeHaveCacheDTRec$inequality)
      if (CRANvOKbcNeedLatest %in% FALSE)
        CRANvOKbcNeedLatest <- is.na(maybeHaveCacheDTRec$inequality)
      if (isTRUE(any(!CRANvOKbcNeedLatest))) {
        #  This is anything that has an inequality or is NOT on CRAN
        isGitHead <- maybeHaveCacheDTRec$versionSpec %in% "HEAD" & maybeHaveCacheDTRec$repoLocation %in% "GitHub"
        if (any(isGitHead)) {
          maybeHaveCacheDTRec[which(isGitHead), correctVersionInCache := FALSE] # force re-check
          # hasVersionNum <- hasVersionNum[!isGitHead]
          CRANvOKbcNeedLatest[!isGitHead] <- CRANvOKbcNeedLatest[!isGitHead]
        }
        maybeCRANOKNeedVerCheck <- CRANvOKbcNeedLatest %in% FALSE

        # correctVersionInCache -- if versionSpec is satisfied
        if (sum(maybeCRANOKNeedVerCheck)) {

          whMaybeCRANOK <- which(maybeCRANOKNeedVerCheck)
          isHEAD <- maybeHaveCacheDTRec$versionSpec[whMaybeCRANOK] %in% "HEAD"
          if (isTRUE(any(isHEAD %in% TRUE))) {
            maybeHaveCacheDTRec[whMaybeCRANOK[isHEAD], correctVersionInCache := FALSE]
          }
          if (isTRUE(any(isHEAD %in% FALSE))) {
            maybeHaveCacheDTRec[whMaybeCRANOK[isHEAD %in% FALSE],
                                correctVersionInCache := compareVersion2(version, versionSpec, inequality)]
          }

        }

        # correctVersionInCache -- if CRANvOKbcNeedLatest
        if (sum(CRANvOKbcNeedLatest %in% TRUE))
          set(maybeHaveCacheDTRec, which(CRANvOKbcNeedLatest), "correctVersionInCache", TRUE)

        correctV <- maybeHaveCacheDTRec[["correctVersionInCache"]] %in% TRUE
        haveCache <- correctV | is.na(maybeHaveCacheDTRec[["correctVersionInCache"]])
        correct <- correctV | CRANvOKbcNeedLatest
        maybeHaveCacheDTRec <- maybeHaveCacheDTRec[which(correct)]
        getFromCurCachePoss <- correct[which(correct)]
      }

      if (any(haveCache)) {

        set(maybeHaveCacheDTRec, NULL, "toGet", getFromCurCachePoss)

        whHaveCacheDT <- which(maybeHaveCacheDTRec[["toGet"]])
        whHaveCachePkgDT <-
          match(maybeHaveCacheDTRec[["packageFullName"]][whHaveCacheDT], pkgDT[["packageFullName"]])

        if (recursive %in% FALSE) {
          noColYet <- pkgDT[whHaveCachePkgDT][[deps(recursive)]]
          if (is.null(noColYet)) {
            a <- mget(maybeHaveCacheDTRec$curCache[whHaveCacheDT], envir = envPkgDepDeps())
            addEmpty <- FALSE
            if (length(a) <= 2) addEmpty <- is.null(unlist(a))
            if (addEmpty)
              a <- lapply(seq(NROW(pkgDT)), function(x) emptyDT())
            set(pkgDT, whHaveCachePkgDT, deps(recursive), unname(a))
          } else {
            # a <- lapply(whHaveCachePkgDT, function(x) data.table())#list(Package = NULL)
          }
        } else {
          objs <- mget(maybeHaveCacheDTRec$curCache[whHaveCacheDT], envir = envPkgDepDeps()) |>
            unname()
          # if (!identical(objs, pkgDT[[deps(recursive)]][whHaveCachePkgDT])) {
          set(pkgDT, whHaveCachePkgDT, deps(recursive), objs)
          # }
        }

        set(pkgDT, whHaveCachePkgDT, cached(recursive), TRUE)
        set(pkgDT, whHaveCachePkgDT, sn(recursive), maybeHaveCacheDTRec$curCache[whHaveCacheDT])
      }
    }
  # }
  pkgDT
}

emptyDT <- function()
  list(Package = character(), packageFullName = character(), which = character()) |> setDT()

grepV <- Vectorize(grep, vectorize.args = "pattern", SIMPLIFY = TRUE)
greplVint <- Vectorize(grepl, vectorize.args = "pattern", SIMPLIFY = TRUE)
greplV <- function(pattern, x, ...) {
  ww <- greplVint(pattern, x, ...)
  if (!is.matrix(ww)) ww <- t(as.matrix(ww))
  ww
}

allNotNull <- function(x) {
  ret <- FALSE
  if (!is.null(x))
    ret <- all(x %in% TRUE)
  ret
}

isNotNULLAnd <- function(x, cond) {
  ret <- FALSE
  if (!is.null(x))
    ret <- eval(cond)
  ret
}



appendRecursiveToDeps <- function(pkgDT, caTr, depFa, caFa, snFa, depTr, snTr) {
  if (!is.null(pkgDT[["TRUE"]][[depTr]]))
    set(pkgDT[["TRUE"]], NULL, depTr, NULL)

  if (!is.null(pkgDT[["TRUE"]][[caTr]]))
    set(pkgDT[["TRUE"]], NULL, caTr, NULL)

  if (!is.null(pkgDT[["TRUE"]][[snTr]]))
    set(pkgDT[["TRUE"]], NULL, snTr, NULL)

  chColNames <- c(depFa, caFa, snFa) %in% colnames(pkgDT[["TRUE"]])
  if (any(chColNames)) {
    setnames(pkgDT[["TRUE"]],
             old = c(depFa, caFa, snFa)[chColNames],
             new = c(depTr, caTr, snTr)[chColNames])

    set(pkgDT[["TRUE"]],  NULL, snTr, gsub("FALSE$", "TRUE", pkgDT[["TRUE"]][[snTr]]))
  }

  needFlatten <- sapply(pkgDT[["TRUE"]][[depTr]],
                        function(x) {
                          !is.null(x[[depTr]])
                          })
  if (sum(needFlatten)) {
    whNeedFlatten <- which(needFlatten)
    set(pkgDT[["TRUE"]], whNeedFlatten, depTr,
        lapply(whNeedFlatten,
               function(ind) {
                 rbindlistRecursive(
                   append(list(pkgDT[["TRUE"]][[depTr]][[ind]]),
                        pkgDT[["TRUE"]][[depTr]][[ind]][[depTr]])
                   )
                 })

    )

  }


  pkgDT <- rbindlist(pkgDT, fill = TRUE)
  if (!is.null(pkgDT[[depFa]]))
    set(pkgDT, NULL, depFa, NULL) # remove non-recursive column for clearning memory
  if (!is.null(pkgDT[[snFa]]))
    set(pkgDT, NULL, snFa, NULL) # remove non-recursive column for clearning memory
  if (!is.null(pkgDT[[caFa]]))
    set(pkgDT, NULL, caFa, NULL) # remove non-recursive column for clearning memory
  pkgDT[]
}


#' @include pkgDep.R
#' @importFrom utils packageDescription
RequireDependencies <- function(libPaths = .libPaths()) {
  localRequireDir <- file.path(libPaths, "Require")
  de <- dir.exists(localRequireDir)
  RequireDeps <- character()
  if (any(de)) {
    localRequireDir <- localRequireDir[de][1]
    # The next line is `try` because during R CMD check, the folder is there, but no DESCRIPTION file yet
    RequireDeps <- try(DESCRIPTIONFileDeps(file.path(localRequireDir, "DESCRIPTION")), silent = TRUE)
  }
  if (is(RequireDeps, "try-error") || all(de %in% FALSE)) {
    # if the package is loaded to memory from a different .libPaths() that is no longer on the current .libPaths()
    #  then the next line will work to find it
    deps <- packageDescription("Require", lib.loc = NULL, fields = "Imports")
    if (nzchar(deps)) {
      RequireDeps <- depsWithCommasToVector("Require", depsWithCommas = deps)
    } else {
      RequireDeps <- pkgDep("Require", simplify = TRUE, verbose = 0)
    }
  }
  RequireDeps <- unique(c("Require", unlist(RequireDeps)))
  # RequireDeps <- c(RequireDeps, "sys")
  RequireDeps
}


.RequireDependencies <- character()
.RequireDependenciesNoBase <- character()

.DESCFileFull <- function(PackageUrl, verbose, Repository = NULL, Package, tmpdir) {

  tf <- if (file.exists(PackageUrl)) {
    PackageUrl  # already a full path to an existing local file
  } else if (!is.null(Repository) && !is.na(Repository) && nzchar(Repository)) {
    file.path(cachePkgDirForRepo(Repository, create = TRUE), basename(PackageUrl))
  } else {
    ## Legacy non-pak DESCRIPTION downloader: keep tarballs alongside
    ## Require's other bookkeeping (pak's cache is opaque to us here).
    file.path(.requirePkgInfoDir(create = TRUE), basename(PackageUrl))
  }
  rmEmptyFiles(tf)
  for (attempt in 1:2) {
    out <- if (file.exists(tf)) { NULL } else {
      # This section should only happen if Require.installPackageSys < 1
      for (i in 1:2) { # can be flaky -- try 2x
        inn <- try(download.file(quiet = verbose <= 0 || verbose >= 5,
                                 url = if (startsWith(Repository, "file:"))
                                   file.path(Repository, basename(PackageUrl))
                                 else
                                   file.path(Repository, PackageUrl),
                                 destfile = tf), silent = TRUE)
        if (!is(inn, "try-error"))
          break
      }
      inn
    }
    if (is(out, "try-error")) {
      messageVerbose(out, verbose = verbose)
      out <- NA
    } else {
      DESCFile <- file.path(Package, "DESCRIPTION")
      # while(ret <- )
      ret <- untar(tf, files = DESCFile, exdir = tmpdir)
      if (ret > 0) {
        # means an error
        redownload <- TRUE
        unlink(tf, force = TRUE)
      } else {
        out <- file.path(tmpdir, DESCFile)
        break
      }

    }
  }
  out
}



forceEqualitiesIfAnyAndPoss <- function(pkgDTEqualities) {
  pkgDTFull <- rbindlist(pkgDTEqualities[[depFa]], fill = TRUE)
  bb <- rbindlist(list(pkgDTFull, pkgDTEqualities[, -grep(depFa, colnames(pkgDTEqualities)), with = FALSE]),
                  fill = TRUE) # combine
  cc <- trimRedundancies(bb)
  ineq <- cc$inequality %in% "=="
  pkgDTEquals <- cc[ineq]
  # pkgDTEquals <- cc[, c("Package", "packageFullName", "inequality", "versionSpec")][ineq]
  .pkgEnv <- pkgEnv()
  if (is.null(.pkgEnv[["pkgDTEqualities"]])) {
    .pkgEnv[["pkgDTEqualities"]] <- pkgDTEquals
    on.exit2(.pkgEnv[["pkgDTEqualities"]] <- NULL)

  } else {
    alreadyIn <- pkgDTEquals$packageFullName %in% .pkgEnv[["pkgDTEqualities"]]$packageFullName
    if (!all(alreadyIn)) {
      .pkgEnv[["pkgDTEqualities"]] <-
        rbindlist(list(.pkgEnv[["pkgDTEqualities"]], pkgDTEquals), fill = TRUE)
    }
    pkgDTEquals <- .pkgEnv[["pkgDTEqualities"]]
  }
  # pfn <- cc$packageFullName[ineq]
  # Find and use equalities, if they are there

  if (isTRUE(depFa %in% colnames(pkgDTEqualities))) {
    pkgDTEqualities[[depFa]] <-
      Map(pkgDTinner = pkgDTEqualities[[depFa]], function(pkgDTinner) {
        needsUpdate <- match(pkgDTinner$Package, pkgDTEquals$Package) |> na.omit()
        if (length(needsUpdate)) {
          ee <- rbindlist(list(pkgDTinner, pkgDTEquals[needsUpdate]), fill = TRUE)
          pkgDTinner <- trimRedundancies(ee)
        }
        pkgDTinner
      })
  }
  pkgDTEqualities
}

depTr <- deps(TRUE)
depFa <- deps(FALSE)
snTr <- sn(TRUE)
snFa <- sn(FALSE)
caFa <- cached(FALSE)
caTr <- cached(TRUE)


on.exit2 <- function(expr, envir = sys.frame(-2), add = TRUE, after = TRUE) {
  funExpr <- as.call(list(function() expr))
  do.call(base::on.exit, list(funExpr, add, after), envir = envir)
}
