utils::globalVariables(
  c("Additional_repositories", "githubPkgName",
    "localBranch", "localFiles", "localRepo", "installedSha", "localUsername",
    "newPackageFullName", "..keepNames")
)


#' Reverse package depends
#'
#' This is a wrapper around `tools::dependsOnPkgs`,
#' but with the added option of `topoSort`, which
#' will sort them such that the packages at the top will have
#' the least number of dependencies that are in `packages`.
#' This is essentially a topological sort, but it is done
#' heuristically. This can be used to e.g., `detach` or
#' `unloadNamespace` packages in order so that they each
#' of their dependencies are detached or unloaded first.
#' @inheritParams Require
#' @param deps An optional named list of (reverse) dependencies.
#'   If not supplied, then `tools::dependsOnPkgs(..., recursive = TRUE)`
#'   will be used
#' @param topoSort Logical. If `TRUE`, the default, then
#'   the returned list of packages will be in order with the
#'   least number of dependencies listed in `packages` at
#'   the top of the list.
#' @param reverse Logical. If `TRUE`, then this will use `tools::pkgDependsOn`
#'   to determine which packages depend on the `packages`
#' @param useAllInSearch Logical. If `TRUE`, then all non-core
#' R packages in `search()` will be appended to `packages`
#' to allow those to also be identified
#' @param returnFull Logical. Primarily useful when `reverse = TRUE`.
#'   If `TRUE`, then then all installed packages will be searched.
#'   If `FALSE`, the default, only packages that are currently in
#'   the `search()` path and passed in `packages` will be included
#'   in the possible reverse dependencies.
#'
#' @inheritParams Require
#' @export
#' @rdname pkgDep
#' @return
#' A possibly ordered, named (with packages as names) list where list elements
#' are either full reverse depends.
#'
#' @examples
#' \donttest{
#' if (Require:::.runLongExamples()) {
#'   opts <- Require:::.setupExample()
#'
#'   pkgDepTopoSort(c("Require", "data.table"), reverse = TRUE)
#'
#'   Require:::.cleanup(opts)
#' }
#' }
#'
pkgDepTopoSort <- function(packages,
                           deps,
                           reverse = FALSE,
                           topoSort = TRUE,
                           libPaths,
                           useAllInSearch = FALSE,
                           returnFull = TRUE,
                           recursive = TRUE,
                           purge = getOption("Require.purge", FALSE),
                           which = c("Depends", "Imports", "LinkingTo"),
                           type = getOption("pkgType"),
                           verbose = getOption("Require.verbose"), ...) {

  libPaths <- dealWithMissingLibPaths(libPaths, ...)
  packages <- parseMultiLinePackages(packages)

  if (isTRUE(useAllInSearch)) {
    if (missing(deps)) {
      a <- search()
      a <- setdiff(a, .defaultPackages)
      a <- gsub("package:", "", a)
      packages <- unique(c(packages, a))
    } else {
      messageVerbose(
        "deps is provided; useAllInSearch will be set to FALSE",
        verbose = verbose,
        verboseLevel = 2
      )
    }
  }

  names(packages) <- packages
  if (missing(deps)) {
    aa <- if (isTRUE(reverse)) {
      ip <- .installed.pkgs(lib.loc = libPaths, which = which, collapse = TRUE) # need all installed packages
      ip <- installed.packagesDeps(ip, libPaths = libPaths, which = which[1])
      deps <- depsWithCommasToVector(ip$Package, ip$deps)
      deps <- lapply(deps, extractPkgName)
      # names(deps) <- ip[, "Package"]
      # names(packages) <- packages
      deps <- deps[order(names(deps))]
      revDeps <-
        lapply(packages, function(p) {
          names(unlist(
            lapply(deps, function(d) {
              if (isTRUE(any(p %in% d))) {
                TRUE
              } else {
                NULL
              }
            })
          ))
        })
      if (recursive) {
        revDeps <- lapply(revDeps, function(p) {
          if (!is.null(p)) {
            used <- p
            repeat ({
              r <-
                unique(unlist(lapply(p, function(p1) {
                  names(unlist(
                    lapply(deps, function(d) {
                      if (isTRUE(any(
                        p1 %in% d
                      ))) {
                        TRUE
                      } else {
                        NULL
                      }
                    })
                  ))
                })))

              r <- setdiff(r, used) # addresses circularity

              used <- unique(c(r, used))
              if (length(r) == 0) {
                break
              }

              p <- r
            })
          } else {
            used <- NULL
          }
          sort(used)
        })
      }
    } else {
      pkgDep(
        packages,
        recursive = TRUE,
        purge = purge,
        libPaths = libPaths,
        which = which,
        verbose = verbose,
        includeSelf = FALSE
      )
    }
  } else {
    aa <- deps
  }
  bb <- list()

  aa <- checkCircular(aa)
  cc <- lapply(aa, function(x) {
    character()
  })
  dd <- lapply(cc, function(x) {
    0
  })


  if (length(aa) > 1) {
    lengths <- lengths(aa)
    aa <- aa[order(lengths)]
    cc <- cc[order(lengths)]
    dd <- lapply(cc, function(x) {
      0
    })

    ddIndex <- 0
    priorsBeingInstalled <- priorsAlreadyInstalled <- character()

    if (isTRUE(topoSort)) {
      notInOrder <- TRUE
      isCorrectOrder <- logical(length(aa))
      i <- 1
      newOrd <- numeric(0)
      for (i in seq_along(aa)) {
        dif <- setdiff(seq_along(aa), newOrd)
        pkgNameNames <- extractPkgName(names(aa))
        for (j in dif) {
          pkgName <- extractPkgName(aa[[j]])
          overlapFull <- pkgName %in% pkgNameNames[-i]
          overlap <- pkgName %in% pkgNameNames[dif]
          overlapPkgs <- pkgName[overlapFull]
          isCorrectOrder <- !any(overlap)
          if (isCorrectOrder) {
            cc[j] <- list(overlapPkgs)
            priorsBeingInstalled <-
              vapply(dd, function(x) {
                if (is.numeric(x)) {
                  x == ddIndex
                } else {
                  FALSE
                }
              }, logical(1))
            priorsBeingInstalled <-
              extractPkgName(names(priorsBeingInstalled)[priorsBeingInstalled])
            overlapPkgsAdditional <-
              intersect(overlapPkgs, priorsBeingInstalled)
            if (length(overlapPkgsAdditional)) {
              ddIndex <- ddIndex + 1
              priorsAlreadyInstalled <-
                vapply(dd, function(x) {
                  if (is.numeric(x)) {
                    x < ddIndex
                  } else {
                    FALSE
                  }
                }, logical(1))
              priorsAlreadyInstalled <-
                extractPkgName(names(priorsAlreadyInstalled)[priorsAlreadyInstalled])
            }
            dd[j] <- list(ddIndex)

            newOrd <- c(newOrd, j)
            # i <- i + 1
            break
          }
        }
      }
      aa <- aa[newOrd]
      cc <- cc[newOrd]
      dd <- dd[newOrd]
    }
  }

  out <- if (isTRUE(returnFull)) {
    aa
  } else {
    cc
  }
  attr(out, "installSafeGroups") <- dd

  return(out)
}

.defaultPackages <-
  c(
    ".GlobalEnv",
    "tools:rstudio",
    "package:stats",
    "package:graphics",
    "package:grDevices",
    "package:utils",
    "package:datasets",
    "package:methods",
    "Autoloads",
    "package:base",
    "devtools_shims"
  )


# @inheritParams Require
# pkgDepCRANInner <-
#   function(ap,
#            which,
#            pkgDT,
#            repos,
#            purge,
#            type,
#            # pkgsNoVersion,
#            # keepVersionNumber,
#            # keepSeparate = FALSE,
#            verbose = getOption("Require.verbose")) {
#
#     if (is.null(pkgDT$Depends)) {
#       if (missing(ap))
#         ap <- getAvailablePackagesIfNeeded(pkgDT$Package, repos = repos, purge = purge, verbose = verbose, type = type)
#       keepNames <- c("Package", setdiff(colnames(pkgDT), colnames(ap)))
#       pkgDT <- ap[pkgDT[, ..keepNames], on = "Package"]
#     }
#
#     pkgDT[is.na(versionSpec), availableVersionOK := TRUE]
#     pkgDT[!is.na(versionSpec), availableVersionOK := compareVersion2(Version, versionSpec, inequality)]
#     for (co in which)
#       set(pkgDT, which(is.na(pkgDT[[co]])), co, "")
#
#     pkgDT[, deps := do.call(paste, append(.SD, list(sep = comma))), .SDcols=which]
#
#     # remove trailing and initial commas
#     set(pkgDT, NULL, "deps", gsub("(, )+$", "", pkgDT$deps))
#     set(pkgDT, NULL, "deps", gsub("^(, )+", "", pkgDT$deps))
#
#     deps <- Map(pkgFN = pkgDT$packageFullName, x = pkgDT$deps, function(pkgFN, x) {
#       out <- strsplit(x, split = "(, {0,1})|(,\n)")[[1]]
#       out <- out[!is.na(out)]
#       out <-
#         grep(.grepR, out, value = TRUE, invert = TRUE)
#       out
#     })
#     depsAll <- Map(toPkgDepDT, deps, verbose = verbose)
#     depsAll
#   }

DESCRIPTIONFileDeps <-
  function(desc_path,
           which = c("Depends", "Imports", "LinkingTo"),
           keepVersionNumber = TRUE,
           purge = getOption("Require.purge", FALSE),
           keepSeparate = FALSE) {
    if (is.null(pkgDepEnv())) {
      envPkgDepCreate()
    }
    if (is.null(envPkgDepDESCFile())) {
      envPkgDepDESCFileCreate()
    }
    objName <- if (length(desc_path) == 1) {
      paste0(
        desc_path,
        paste0(collapse = "_", which, "_", keepVersionNumber)
      )
    } else {
      grepPackage <- "Package *: *"
      grepVersion <- "Version *: *"
      suppressWarnings({
        pkg <-
          gsub(
            grepPackage,
            "",
            grep(grepPackage, desc_path, value = TRUE)
          )
      })
      suppressWarnings({
        vers <-
          gsub(
            grepVersion,
            "",
            grep(grepVersion, desc_path, value = TRUE)
          )
      })
      paste(pkg,
        vers,
        sep = "_",
        paste0(which, "_", keepVersionNumber, collapse = "_")
      )
    }
    if (isTRUE(length(objName) > 1) ||
      isTRUE(any(!exists(objName, envir = envPkgDepDESCFile()))) ||
      isTRUE(purge)) {
      if (is.null(desc_path)) {
        needed <- NULL
      } else {

        lines <- if (length(desc_path) == 1) {
          # linesAll <- lapply(desc_path, read.dcf)
          try(readLines(desc_path), silent = TRUE)
        } else {
          lines <- desc_path
        }
        if (is(lines, "try-error")) {
          stop("Cannot read the file: ", desc_path)
        }
        Sys.setlocale(locale = "C") # required to deal with non English characters in Author names
        on.exit(Sys.setlocale(locale = ""))
        sl <- list()
        sl[["Depends"]] <- grep("^Depends: *", lines) # nolint
        sl[["Suggests"]] <- grep("^Suggests: *", lines) # nolint
        sl[["Imports"]] <- grep("^Imports: *", lines) # nolint
        sl[["Linkingto"]] <- grep("^LinkingTo: *", lines) # nolint
        sl[["Remotes"]] <- grep("^Remotes: *", lines) # nolint
        sl[["colon"]] <- grep(": *", lines) # nolint
        # sl[["Additional_repositories"]] <- grep("^Additional_repositories: *", lines) # nolint

        which <-
          paste0(toupper(substr(which, 1, 1)), substr(which, 2, 1e4))
        which <- unique(which)
        whichLower <- tolower(which)
        needed <-
          Map(#whLower = whichLower,
              # wh = which, function(whLower, wh) {
                wh = which, function(wh) {
            if (length(sl[[wh]])) {
              whEnd <- which(sl[["colon"]] %in% sl[[wh]]) + 1
              whEnd <- if (length(sl[["colon"]]) < whEnd) {
                length(lines)
              } else {
                (sl[["colon"]][whEnd] - 1)
              }
              allLines <- sl[[wh]]:whEnd # nolint
              needs <- paste(lines[allLines], collapse = "")
              needs <- gsub(paste0(wh, ": *"), "", needs)
              needs <- strsplit(needs, split = ", *")
              needs <-
                gsub(" *$", "", needs[[1]]) # remove trailing spaces
              if (isFALSE(keepVersionNumber)) {
                needs <- trimVersionNumber(needs)
              }
              needs
            }
          })
        if (length(objName) == 1) {
          assign(objName, needed, envir = envPkgDepDESCFile())
        }
      }
    } else {
      needed <-
        get(objName, envir = envPkgDepDESCFile())
    }
    if (!keepSeparate) {
      needed <- unname(unlist(needed))
      needed <- grep("^R[\\( ]", needed, value = TRUE, invert = TRUE)
    }
    needed
  }

DESCRIPTIONFile <- function(desc_path) {
  lines <- readLines(desc_path)
}

whichToDILES <- function(which) {
  if (identical("all", which) || is.null(which)) {
    which <-
      list(c("Depends", "Imports", "LinkingTo", "Suggests", "Enhances"))
  } else if (identical("most", which)) {
    which <- list(c("Depends", "Imports", "LinkingTo", "Suggests"))
  } else if (isTRUE(which)) {
    which <- list(
      c("Depends", "Imports", "LinkingTo", "Suggests"),
      c("Depends", "Imports", "LinkingTo")
    )
  } else if (any(is.na(which))) {
    which <- list(c("Depends", "Imports", "LinkingTo"))
  } else if (is.character(which)) {
    which <- list(which)
  } else if (isFALSE(which)) {
    which <- character()
  }
  which
}

#' Partial alternative (faster) to `installed.packages`
#'
#' This reads the DESCRIPTION files only, so can only access fields that are
#' available in the DESCRIPTION file. This is different than `installed.packages`
#' which has many other fields, like "Built", "NeedsCompilation" etc. If those
#' fields are needed, then this function will return an empty column in the returned
#' character matrix.
#' @inheritParams utils::installed.packages
#' @inheritParams pkgDep
#' @param other Can supply other fields; the only benefit here is that a user
#'   can specify `"github"` (lower case) and it will automatically add
#'   c("GithubRepo", "GithubUsername", "GithubRef", "GithubSHA1",
#'   "GithubSubFolder") fields
#' @param collapse Logical. If `TRUE` then the dependency fields will be collapsed;
#'   if `FALSE` (default) then the `which` fields will be kept separate.
#' @param packages Character vector. If `NULL` (default), then all installed packages
#'   are searched for. If a character vector is supplied, then it will only return
#'   information about those packages (and is thus faster to execute).
.installed.pkgs <-
  function(lib.loc = .libPaths(),
           which = c("Depends", "Imports", "LinkingTo"),
           other = NULL,
           purge = getOption("Require.purge", FALSE),
           packages = NULL,
           collapse = FALSE) {
    purge <- dealWithCache(purge)
    if (!is.null(other)) {
      hasGit <- grepl("github", tolower(other))
      if (any(hasGit)) {
        other <- c(
          c("GithubRepo", "GithubUsername", "GithubRef", "GithubSHA1",
            "GithubSubFolder"),
          other[!hasGit])
      }
    }

    out <- lapply(lib.loc, function(path) {
      dirs <- dir(path, full.names = TRUE)
      # from pak -- makes a _cache which isn't relevant here
      dirs <- dirs[!endsWith(dirs, suffix = "_cache")]
      mat <- NULL
      if (length(dirs)) {
        if (!is.null(packages)) {
          keeps <- basename(dirs) %in% packages
          dirs <- dirs[keeps]
        }
        areDirs <- dir.exists(dirs)
        dirs <- dirs[areDirs]
        files <- file.path(dirs, "DESCRIPTION")
        filesExist <- file.exists(files)
        files <- files[filesExist]
        names(files) <- basename(dirs[filesExist])

        desc_lines <-
          lapply(files, function(file) {
            DESCRIPTIONFile(file)
          })
        versions <- DESCRIPTIONFileVersionV(desc_lines, purge = FALSE)
        deps <- if (length(which)) {
          names(which) <- which
          deps <- lapply(desc_lines, function(lines) {
            if (isTRUE(collapse))
              Map(a = "Depends", function(a) paste(DESCRIPTIONFileDeps(lines, which = which, purge = purge), collapse = comma))
            else
              lapply(which, function(wh)
                paste(DESCRIPTIONFileDeps(lines, which = wh, purge = purge), collapse = comma)
              )
          })
          if (length(deps))
            deps <- invertList(deps)
          deps
        } else {
          NULL
        }
        if (!is.null(other)) {
          names(other) <- other
          others <-
            lapply(other, function(oth) {
              DESCRIPTIONFileOtherV(desc_lines, other = oth)
            })
          # shas <- DESCRIPTIONFileOtherV(desc_lines)
        }
        mat <-
          cbind(
            "Package" = dirs[filesExist],
            "Version" = versions
          )
        if (!is.null(unlist(deps))) {
          cn <- c(colnames(mat), names(deps))
          mat <- cbind(mat, matrix(unlist(deps), ncol = length(deps)))
          colnames(mat) <- cn
        }
        if (!is.null(other)) {
          others <- lapply(others, function(co) {
            if (!is(co, "character")) {
              as.character(co)
            } else {
              co
            }
          })
          othersDF <- as.data.frame(others, stringsAsFactors = FALSE)
          mat <- cbind(mat, othersDF, stringsAsFactors = FALSE)
        }
      }
      mat
    })

    lengths <- unlist(lapply(out, function(x) {
      NROW(x)
    }))

    out <- do.call(rbind, out)
    colNames <- c("Package", "Version", "LibPath", which, other)
    if (is.null(out)) {
      out <- matrix(data = character(), ncol = 3 + length(which) + length(other))
      colnames(out) <- colNames
    } else {
      out[, "Package"] <- basename(out[, "Package"])
      out <- cbind(out, "LibPath" = rep(lib.loc, lengths), stringsAsFactors = FALSE)

      dups <- duplicated(out[, "Package"]) # means installed in >1 .libPaths()
      out <- out[!dups, , drop = FALSE]
    }
    colNames <- intersect(colNames, colnames(out))
    out <- out[, colNames, drop = FALSE]
    # ret <-
    #   cbind(
    #     "Package" = basename(unlist(out[, "Package"])),
    #     "LibPath" = rep(lib.loc, lengths),
    #     "Version" = out[, "Version"]
    #   )
    # if (length(which)) {
    #   ret <-
    #     cbind(ret,
    #       "Dependencies" = out[, "Depends"],
    #       stringsAsFactors = FALSE
    #     )
    # }
    # if (NROW(ret)) {
    #   if (!is.null(other)) {
    #     ncolBefore <- NCOL(ret)
    #     ret <- cbind(ret, out[, other], stringsAsFactors = FALSE)
    #   }
    # }
    out
  }

.basePkgs <-
  c(
    "base",
    "boot",
    "class",
    "cluster",
    "codetools",
    "compiler",
    "datasets",
    "foreign",
    "graphics",
    "grDevices",
    "grid",
    "KernSmooth",
    "lattice",
    "MASS",
    "Matrix",
    "methods",
    "mgcv",
    "nlme",
    "nnet",
    "parallel",
    "rpart",
    "spatial",
    "splines",
    "stats",
    "stats4",
    "survival",
    "tcltk",
    "tools",
    "translations",
    "utils"
  )

DESCRIPTIONFileDepsV <-
  Vectorize(DESCRIPTIONFileDeps,
    vectorize.args = "desc_path",
    SIMPLIFY = FALSE
  )

#' Package dependencies when one or more packages removed
#'
#' This is primarily for package developers. It allows the testing of what the
#' recursive dependencies would be if a package was removed from the immediate
#' dependencies.
#' @param pkg A package name to be testing the dependencies
#' @param depsRemoved A vector of package names who are to be "removed" from the
#'   `pkg` immediate dependencies
#' @export
#' @return A list with 3 named lists `Direct`, `Recursive` and `IfRemoved`.
#' `Direct` will show the top level direct dependencies, either `Remaining` or
#' `Removed`. `Recursive` will show the full recursive dependencies, either
#' `Remaining` or `Removed`. `IfRemoved` returns all package dependencies that
#' are removed for each top level dependency. If a top level dependency is not
#' listed in this final list, then it means that it is also a recursive
#' dependency elsewhere, so its removal has no effect.
#' @inheritParams Require
#' @examples
#' \donttest{
#' if (Require:::.runLongExamples()) {
#'   opts <- Require:::.setupExample()
#'
#'   pkgDepIfDepRemoved("reproducible", "data.table")
#'
#'   Require:::.cleanup(opts)
#' }
#' }
#'
pkgDepIfDepRemoved <-
  function(pkg = character(),
           depsRemoved = character(),
           verbose = getOption()) {
    if (length(pkg)) {
      p2 <- pkgDep2(pkg, recursive = TRUE)
      p2 <-
        Map(p = p2, nam = names(p2), function(p, nam) {
          setdiff(p, nam)
        })
      names(p2) <- extractPkgName(names(p2))
      p1 <- names(p2) # just the immediate
      p3 <- p2
      p2All <-
        unique(extractPkgName(c(names(p2), unname(unlist(
          p2
        )))))
      p3[depsRemoved] <- NULL
      p3All <-
        unique(extractPkgName(c(names(p3), unname(unlist(
          p3
        )))))
      removed <- sort(setdiff(p2All, p3All))
      left <- sort(p3All)

      removedDirect <- intersect(p1, depsRemoved)
      allInRecursive <- unique(extractPkgName(unname(unlist(p2))))
      direct <- p1
      bestToRemove <- setdiff(direct, allInRecursive)
      bestToRemove <- p2[bestToRemove]
      bestToRemove <-
        Map(
          btr = bestToRemove, top = names(bestToRemove),
          function(btr, top) {
            p4 <- p2
            p4[top] <- NULL
            setdiff(
              extractPkgName(unique(c(
                names(p4), unname(unlist(p4))
              ))),
              extractPkgName(c(top, btr))
            )
          }
        )
      leftDirect <- names(p3)
      pkg <-
        list(
          Direct = list(
            Remaining = leftDirect,
            Removed = removedDirect,
            NRemaining = length(leftDirect),
            NRemoved = length(removedDirect)
          ),
          Recursive = list(
            "Remaining" = left,
            "Removed" = removed,
            NRemaining = length(left),
            NRemoved = length(removed)
          ),
          IfRemoved = bestToRemove
        )
    }
    return(pkg)
  }

checkCircular <- function(aa) {
  circular <- lapply(names(aa), function(outer) {
    circularWh <- extractPkgName(aa[[outer]]) %in% extractPkgName(outer)
    circ <- if (any(circularWh)) {
      aa[[outer]][circularWh]
    } else {
      NULL
    }
  })

  if (length(unlist(circular))) {
    for (i in which(!unlist(lapply(circular, is.null)))) {
      aa[[i]] <- setdiff(aa[[i]], circular[[i]])
    }
  }
  aa
}

# @inheritParams Require
# getGitHubDeps <-
#   function(pkg,
#            pkgDT,
#            which,
#            purge,
#            verbose = getOption("Require.verbose"),
#            includeBase = FALSE) {
#     pkg <- masterMainToHead(pkgDT$packageFullName)
#
#     localVersionOK <- pkgDT$installedVersionOK
#     pkgDepDTOuter <- data.table(packageFullName = character())
#
#     if (any(!localVersionOK %in% TRUE)) {
#       pkgDTNotLocal <- dlGitHubDESCRIPTION(pkgDT[!localVersionOK %in% TRUE], purge = purge)
#     }
#     if (any(localVersionOK %in% TRUE)) {
#       pkgDT[localVersionOK %in% TRUE, DESCFile := base::system.file("DESCRIPTION", package = Package), by = "Package"]
#       if (exists("pkgDTNotLocal", inherits = FALSE))
#         pkgDT <- rbindlist(list(pkgDT[localVersionOK %in% TRUE], pkgDTNotLocal), fill = TRUE, use.names = TRUE)
#     } else {
#       pkgDT <- pkgDTNotLocal
#     }
#
#     hasVersionNum <- pkgDT$hasVers # grep(grepExtractPkgs, pkgDT$packageFullName)
#     set(pkgDT, NULL, "availableVersionOK", NA)
#     if (any(hasVersionNum)) {
#       if (!isTRUE(getOption("Require.offlineMode"))) {
#         pkgDT[hasVersionNum, VersionOnRepos := DESCRIPTIONFileVersionV(DESCFile, purge = purge)]
#         if (is.null(pkgDT$versionSpec)) {
#           pkgDT[hasVersionNum, versionSpec := extractVersionNumber(packageFullName)]
#           pkgDT[hasVersionNum, inequality := extractInequality(packageFullName)]
#         }
#         pkgDT[hasVersionNum, availableVersionOK := compareVersion2(VersionOnRepos, versionSpec, inequality)]
#       }
#     }
#
#
#     if (any(!pkgDT$availableVersionOK %in% FALSE)) {
#       neededV <-
#         try(DESCRIPTIONFileDepsV(pkgDT$DESCFile, which = which, purge = purge))
#       if (is(neededV, "try-error")) {
#         unlink(pkgDT$DESCFile)
#         unlink(pkgDT$destFile)
#         set(pkgDT, NULL, c("DESCFile", "destFile"), NULL)
#         browserDeveloper(paste0("A problem occurred installing ", pkgDT$packageFullName, ". Does it exist?",
#                                 "\nTo confirm whether it exists, try browsing to ",
#                                 file.path("https://github.com", pkgDT$Account, pkgDT$Package, "tree", pkgDT$Branch),
#                                 "\nIf it does exist, try rerunning with `purge = TRUE`",
#                                 "\nIf this error is inaccurate, and the problem persists, ",
#                                 "please contact developers with error code 949"))
#       }
#
#       neededAdditionalReposV <- DESCRIPTIONFileOtherV(pkgDT$DESCFile, other = "Additional_repositories")
#
#       neededRemotesV <-
#         DESCRIPTIONFileDepsV(pkgDT$DESCFile, which = "Remotes", purge = purge)
#       names(neededV) <- pkgDT$packageFullName
#
#       pkgDepDTOuter <- updateWithRemotesNamespaceAddRepos(neededV, neededRemotesV, pkgDT, neededAdditionalReposV, includeBase, verbose)
#     }
#
#     pkgDepDTOuter
#   }

purgeBasedOnTimeSinceCached <- function(savedTime) {
  purgeDiff <-
    as.numeric(Sys.getenv("R_AVAILABLE_PACKAGES_CACHE_CONTROL_MAX_AGE"))
  if (is.null(savedTime)) {
    purge <- TRUE
  } else {
    purgeDiff <-
      if (identical(purgeDiff, "") ||
          is.na(purgeDiff)) {
        defaultCacheAgeForPurge
      } else {
        purgeDiff
      }
    purge <-
      purgeDiff < as.numeric(difftime(Sys.time(), savedTime, units = "sec"))
  }
  purge
}

defaultCacheAgeForPurge <- 3600

#' Purge everything in the Require cache
#'
#' Clears Require's own bookkeeping caches: the `available.packages()`
#' snapshot, the GitHub SHA database, the `DESCRIPTION` cache, and the
#' pkgDep memoisation database. These live under [cacheDir()] and are
#' distinct from the package binary tarball cache.
#'
#' With `packages = TRUE`, the package binary cache is also cleared by
#' calling [cacheClearPackages()] -- which under `usePak = TRUE` (the
#' default) delegates to `pak::cache_clean()`, and under the legacy path
#' walks Require's bookkeeping dir directly.
#'
#' `purgeCache()` is a deprecated alias.
#'
#' @inheritParams Require
#' @return Run for its side effect, namely, all cached objects are removed.
#'
#' @export
cachePurge <- function(packages = FALSE,
                       repos = getOption("repos")) {
  if (isTRUE(packages))
    Require::cacheClearPackages(ask = FALSE)
  dealWithCache(TRUE, repos = repos)
}

#' @rdname cachePurge
#' @export
purgeCache <- function(packages = FALSE, repos = getOption("repos")) {
  .Deprecated("cachePurge", package = "Require",
              msg = "purgeCache() is deprecated; use cachePurge() instead.")
  cachePurge(packages = packages, repos = repos)
}

dealWithCache <- function(purge = TRUE,
                          checkAge = TRUE,
                          repos = getOption("repos"),
                          force = FALSE) {
  if (isTRUE(getOption("Require.offlineMode"))) {
    purge <- FALSE
    checkAge <- FALSE
  }

  if (isTRUE(force)) {
    purge <- TRUE
    checkAge <- TRUE
  }

  purgeBasedOnTime <- FALSE
  if (!isTRUE(purge) && isTRUE(checkAge)) {
    ee <- pkgEnvStartTime()
    purgeBasedOnTime <- if (is.null(ee)) FALSE else purgeBasedOnTimeSinceCached(ee)
  }
  purge <- purge || purgeBasedOnTime

  if (purge) {
    unlink(availablePackagesCachedPath(repos, type = c("binary", "source")))
    Sys.setenv("R_AVAILABLE_PACKAGES_CACHE_CONTROL_MAX_AGE" = 0)
    pkgEnvStartTimeCreate()
    unlink(dir(RequireGitHubCacheDir(), full.names = TRUE))
    # getSHAFromGItHubMemoise
    SHAfile <- getSHAFromGitHubDBFilename()
    if (isTRUE(file.exists(SHAfile)))
      unlink(SHAfile)

    purgeAvailablePackages(repos, purge)

    # This is for pkgDep
    fn <- pkgDepDBFilename()
    unlink(fn)
  }

  if (is.null(pkgDepEnv()))
    envPkgDepCreate()

  if (is.null(envPkgDepGitHubSHA()))
    envPkgDepGitHubSHACreate()

  if (is.null(envPkgDepDeps()))
    envPkgDepDepsCreate()

  if (is.null(envPkgDepDESCFile()))
    envPkgDepDESCFileCreate()

  if (is.null(envPkgDepArchiveDetailsInner()))
    envPkgDepArchiveDetailsInnerCreate()

  purgePkgDep(purge)

  purge
}

.grepTooManySpaces <- " {2,}"
.grepTabCR <- "\n|\t"



purgePkgDep <- function(purge) {
  if (isTRUE(purge)) {
    envPkgDepCreate()
    envPkgDepGitHubSHACreate()
    envPkgDepDepsCreate()
    envPkgDepDESCFileCreate()
    envPkgDepArchiveDetailsInnerCreate()
  }
  purge
}

#' This converts master or main to HEAD for a git repo
#'
#' This will also convert a git repo with nothing after the @ to @HEAD
#' @param gitRepo A git repository of the form account/repo with optional @branch
#'    or @sha or @tag
#' @return The git repository with @HEAD if it had @master, @main or no @.
masterMainToHead <- function(gitRepo) {
  masterOrMain <- "@(master|main) *"
  areMasterOrMain <- grepl(masterOrMain, gitRepo)
  if (any(areMasterOrMain)) {
    masterOrMainNoSpace <- "@(master|main)"
    gitRepo[areMasterOrMain] <-
      gsub(paste0("(", masterOrMainNoSpace, ")"), "@HEAD", gitRepo[areMasterOrMain])
  }

  noAt <- !grepl("@", gitRepo)
  if (any(noAt)) {
    gitRepo[noAt] <- paste0(gitRepo[noAt], "@HEAD")
  }
  return(gitRepo)
}

#' Convert numeric to character with padding
#'
#' This will pad floating point numbers, right or left. For integers, either class
#' integer or functionally integer (e.g., 1.0), it will not pad right of the decimal.
#' For more specific control or to get exact padding right and left of decimal,
#' try the \code{stringi} package. It will also not do any rounding. See examples.
#'
#' @param x numeric. Number to be converted to character with padding
#'
#' @param padL numeric. Desired number of digits on left side of decimal.
#'              If not enough, \code{pad} will be used to pad.
#'
#' @param padR numeric. Desired number of digits on right side of decimal.
#'              If not enough, \code{pad} will be used to pad.
#'
#' @param pad character to use as padding (\code{nchar(pad) == 1} must be \code{TRUE}).
#'   Currently, can be only `"0"` or `" "` (i.e., space).
#'
#' @return Character string representing the filename.
#'
#' @author Eliot McIntire and Alex Chubaty
#' @export
#' @rdname paddedFloatToChar
#'
#' @examples
#' paddedFloatToChar(1.25)
#' paddedFloatToChar(1.25, padL = 3, padR = 5)
#' paddedFloatToChar(1.25, padL = 3, padR = 1) # no rounding, so keeps 2 right of decimal
paddedFloatToChar <-
  function(x,
           padL = ceiling(log10(x + 1)),
           padR = 3,
           pad = "0") {
    if (!pad %in% c("0", " ")) {
      stop("pad must be either '0' or ' '")
    }
    xf <- x %% 1
    numDecimals <- nchar(gsub("(.*)(\\.)|([0]*$)", "", xf))

    newPadR <-
      ifelse(abs(xf - 0) < sqrt(.Machine$double.eps),
        0,
        pmax(numDecimals, padR)
      )
    string <-
      paste0(
        paste0("%", pad),
        padL + newPadR + 1 * (newPadR > 0),
        ".",
        newPadR,
        "f"
      )
    xFCEnd <- sprintf(string, x)
    return(xFCEnd)
  }



pkgDepTopoSortMemoise <- function(...) {
  if (getOption("Require.useMemoise", TRUE)) {
    dots <- list(...)
    fnName <- "pkgDepTopoSort"
    fn <- eval(parse(text = fnName))
    pe <- pkgEnv()
    if (!exists(fnName, envir = pe, inherits = FALSE)) {
      pe[[fnName]] <- new.env()
    }
    ret <- NULL
    ss <- match.call(definition = fn)
    pkg <- eval(ss$pkg, envir = parent.frame())
    hash <-
      sum(as.integer(serialize(
        object = pkg, ascii = TRUE, NULL
      )))
    hash <- as.character(hash)
    if (!exists(hash, envir = pe[[fnName]], inherits = FALSE)) {
      pe[[fnName]][[hash]] <- list()
    } else {
      whIdent <-
        unlist(lapply(pe[[fnName]][[hash]], function(x) {
          identical(x$input, dots)
        }))
      if (any(whIdent)) {
        ret <- pe[[fnName]][[hash]][[which(whIdent)]]$output
      }
    }
    if (is.null(ret)) {
      inputs <- data.table::copy(dots)
      ret <- fn(...)
      pe[[fnName]][[hash]] <-
        list(pe[[fnName]][[hash]], list(input = inputs, output = ret))
    }
  } else {
    ret <- fn(...)
  }

  return(ret)
}

pkgDepDBFilename <- function() {
  ## Require's pkgDep cache file -- bookkeeping, kept next to the legacy
  ## SHA DB so first-run-after-upgrade doesn't lose existing state.
  d <- .requirePkgInfoDir()
  if (!is.null(d) && nzchar(d))
    file.path(d, "pkgDepDB.rds")
  ## else NULL (matches the prior "no Cache used" branch)
}

isAre <- function(l, v) {
  singularPlural(c("is", "are"), l, v)
}

hasHave <- function(l, v) {
  singularPlural(c("has", "have"), l, v)
}

singularPlural <- function(singPlur, l, v) {
  if (!missing(l)) {
    out <- singPlur[(length(l) > 1) + 1]
  }
  if (!missing(v)) {
    out <- singPlur[(v > 1) + 1]
  }
  out
}


prependSelf <- function(deps, includeSelf) {
  if (isTRUE(includeSelf)) {
    deps <- Map(pkgDepDT = deps, nam = names(deps), function(pkgDepDT, nam) {
      depsInner <- pkgDepDT
      alreadyHasSelf <- identical(extractPkgName(pkgDepDT$packageFullName[1]), extractPkgName(nam))
      # alreadyHasSelf <- startsWith(pkgDepDT[1], trimVersionNumber(nam))
      if (!isTRUE(alreadyHasSelf)) {
        depsInner <- rbindlist(list(toPkgDepDT(nam), pkgDepDT), fill = TRUE, use.names = TRUE)
      }

      return(depsInner)
    })
  }
  deps
}

getAvailablePackagesIfNeeded <-
  function(packages, repos, purge, verbose, type) {
    if (is.data.table(packages)) {
      pkgDT <- packages
    } else {
      pkgDT <- parseGitHub(packages)
    }
    isCRAN <- pkgDT[["repoLocation"]] %in% "CRAN"

    if (any(isCRAN %in% TRUE)) {
      ap <-
        available.packagesCached(
          repos = repos,
          purge = purge,
          verbose = verbose,
          type = type
        )
    } else {
      ap <- NULL
    }
    ap
  }

#' Clear cached package tarballs
#'
#' Removes downloaded package archives from the cache that [cachePkgDir()]
#' returns. With `usePak = TRUE` (the default) this delegates to
#' `pak::cache_clean()` (no `packages` arg) or
#' `pak::cache_delete(package = ...)` (selective). With `usePak = FALSE`
#' it walks Require's legacy bookkeeping dir and unlinks tarballs there.
#'
#' Require's own bookkeeping (SHA DB, mirrors.csv, available.packages
#' snapshots) is preserved by this function; use [cachePurge()] to clear
#' those as well.
#'
#' `clearRequirePackageCache()` is a deprecated alias.
#'
#' @section Migration note:
#'
#' The `Rversion` parameter is honoured only on the legacy path -- pak's
#' cache is not partitioned by R version the way Require's cache was, so
#' `pak::cache_clean()`/`pak::cache_delete()` operate on the whole cache
#' regardless of `Rversion`. The function emits a verbose note when
#' `Rversion != versionMajorMinor()` under `usePak = TRUE`.
#'
#' @param packages Either missing or a character vector of package names
#'   (currently cannot specify version number) to remove from the cache.
#' @param ask Logical. If `TRUE`, asks the user to confirm before deleting.
#' @param Rversion An R version (major.minor, e.g., `"4.2"`). Defaults to
#'   the current R version. **Ignored under `usePak = TRUE`** -- see
#'   "Migration note".
#' @param clearCranCache Logical. If `TRUE`, also clears the local
#'   `crancache` cache, which is only relevant if
#'   `options(Require.useCranCache = TRUE)`.
#' @export
#' @inheritParams Require
#' @rdname clearRequire
cacheClearPackages <- function(packages,
                                     ask = interactive(),
                                     Rversion = versionMajorMinor(),
                                     clearCranCache = FALSE,
                                     verbose = getOption("Require.verbose")) {
  ## In pak mode the package tarballs live in pak's cache, not in
  ## Require's bookkeeping dir. Delegate to pak's native API so the
  ## right files actually get removed.
  if (isTRUE(getOption("Require.usePak", TRUE)) &&
      requireNamespace("pak", quietly = TRUE)) {
    if (!identical(Rversion, versionMajorMinor())) {
      messageVerbose(
        "cacheClearPackages: `Rversion` arg ignored under usePak = TRUE -- ",
        "pak's cache is not partitioned by R version the way Require's ",
        "legacy cache was; pak::cache_delete() / pak::cache_clean() ",
        "operate on the full cache.",
        verbose = verbose, verboseLevel = 1
      )
    }
    proceed <- TRUE
    if (isTRUE(ask)) {
      message(if (missing(packages))
                "Are you sure you would like to remove ALL packages from pak's cache?"
              else
                paste0("Are you sure you would like to remove\n",
                       paste(packages, collapse = ", "),
                       "\nfrom pak's cache?"))
      askResult <- readline("(n or anything else for yes) ")
      if (startsWith(tolower(askResult), "n")) proceed <- FALSE
    }
    if (isTRUE(proceed)) {
      if (missing(packages)) {
        messageVerbose("Clearing entire pak download cache via pak::cache_clean()",
                       verbose = verbose, verboseLevel = 1)
        tryCatch(pak::cache_clean(),
                 error = function(e) {
                   warning("pak::cache_clean() failed: ", conditionMessage(e),
                           call. = FALSE)
                 })
      } else {
        messageVerbose("Removing from pak download cache: ",
                       paste(packages, collapse = ", "),
                       verbose = verbose, verboseLevel = 1)
        tryCatch(pak::cache_delete(package = packages),
                 error = function(e) {
                   warning("pak::cache_delete() failed: ", conditionMessage(e),
                           call. = FALSE)
                 })
      }
      ## Also drop Require's own SHA DB so HEAD-pin resolution
      ## reflects the fresh state (mirrors the legacy branch's behavior).
      SHAfile1 <- getSHAFromGitHubDBFilename()
      if (length(SHAfile1) && nzchar(SHAfile1) && isTRUE(file.exists(SHAfile1)))
        unlink(SHAfile1)
    } else {
      message("Aborting")
    }
    return(invisible(NULL))
  }

  ## Legacy non-pak path: walk Require's bookkeeping dir directly.
  out <- .requirePkgInfoDir(create = FALSE)
  if (!identical(Rversion, versionMajorMinor())) {
    out <- file.path(dirname(out), Rversion)
  }
  if (getOption("Require.useCranCache")) {
    crancache <- crancacheFolder()
    toDelete <- dir(crancache, recursive = TRUE, full.names = TRUE)
    if (length(toDelete) && isFALSE(clearCranCache)) {
      messageVerbose(blue(
        "crancache is being used because options(Require.useCranCache = TRUE); ",
        "however, clearCranCache is FALSE. This means that packages from ",
        "crancache will continue to re-populate the Require Cache. ",
        "To remove all local packages, set clearCranCache in this  function to TRUE."
      ))
    }
    if (isTRUE(clearCranCache)) {
      if (!missing(packages)) {
        pkgNamesInFiles <- extractPkgName(filenames = basename(toDelete))
        present <- pkgNamesInFiles %in% packages
        toDelete <- toDelete[present]
      }
      if (length(toDelete)) {
        unlink(toDelete)
      }
    }
  }
  proceed <- TRUE
  # Scan flat dir AND repos-specific subdirs, excluding metadata (.rds) files
  indivFiles <- dir(out, full.names = TRUE, recursive = TRUE)
  isFile <- !dir.exists(indivFiles)
  indivFiles <- indivFiles[isFile & !grepl("\\.rds$", indivFiles)]
  if (missing(packages)) {
    toDelete <-
      indivFiles # don't delete whole dir because has available.packages too; not to delete
    forMess <-
      paste0("all ", length(indivFiles), " cached packages in ", out)
  } else {
    if (length(indivFiles)) {
      pkgNamesInFiles <- extractPkgName(filenames = basename(indivFiles))
      # toDelete <- indivFiles[pkgNamesInFiles %in% packages]
      toDelete <- unlist(grepV(packages, indivFiles, value = TRUE))
      forMess <- paste(sort(basename(toDelete)), collapse = ",\n")
    } else {
      toDelete <- character()
    }
  }
  if (length(toDelete)) {
    if (isTRUE(ask)) {
      message("Are you sure you would like to remove\n", forMess, "\n?")
      askResult <- readline("(n or anything else for yes) ")
      if (startsWith(tolower(askResult), "n")) {
        proceed <- FALSE
      }
    }

    if (isTRUE(proceed)) {
      messageVerbose("Clearing: \n",
                     forMess,
                     verbose = verbose,
                     verboseLevel = 1
      )
      unlink(toDelete, recursive = TRUE)

      # This purges all the pkgDep stuff
      SHAfile1 <- getSHAFromGitHubDBFilename()
      if (isTRUE(file.exists(SHAfile1))) # can be character() ifgetOptionRPackageCache is NULL/FALSE
        unlink(SHAfile1)

    } else {
      message("Aborting")
    }
  } else {
    messageVerbose("Nothing to clear in Cache", verbose = verbose, verboseLevel = 1)
  }
}


#' @export
#' @rdname clearRequire
clearRequirePackageCache <- function(packages,
                                     ask = interactive(),
                                     Rversion = versionMajorMinor(),
                                     clearCranCache = FALSE,
                                     verbose = getOption("Require.verbose")) {
  .Deprecated("cacheClearPackages", package = "Require",
              msg = paste0(
                "clearRequirePackageCache() is deprecated; ",
                "use cacheClearPackages() instead."
              ))
  if (missing(packages)) {
    cacheClearPackages(ask = ask, Rversion = Rversion,
                       clearCranCache = clearCranCache, verbose = verbose)
  } else {
    cacheClearPackages(packages = packages, ask = ask, Rversion = Rversion,
                       clearCranCache = clearCranCache, verbose = verbose)
  }
}

depsImpsSugsLinksToWhich <- function(depends, imports, suggests, linkingTo, which) {
  if (!missing(depends)) {
    wh <-
      "Depends"
    which <- if (isTRUE(depends)) {
      unique(c(which, wh))
    } else {
      setdiff(which, wh)
    }
  }
  if (!missing(imports)) {
    wh <-
      "Imports"
    which <- if (isTRUE(imports)) {
      unique(c(which, wh))
    } else {
      setdiff(which, wh)
    }
  }
  if (!missing(suggests)) {
    wh <-
      "Suggests"
    which <- if (isTRUE(suggests)) {
      unique(c(which, wh))
    } else {
      setdiff(which, wh)
    }
  }
  if (!missing(linkingTo)) {
    wh <-
      "LinkingTo"
    which <- if (isTRUE(linkingTo)) {
      unique(c(which, wh))
    } else {
      setdiff(which, wh)
    }
  }
  whichToDILES(which)
}


installedVersionOKPrecise <- function(pkgDT, libPaths) {
  # pkgload steals system.file but fails under some conditions, not sure what...
  withCallingHandlers(
    pkgDT[, localFiles := base::system.file("DESCRIPTION", package = Package), by = "Package"],
    warning = function(w) {
      if (isTRUE(any(grepl("cannot open compressed file", w$message))))
        invokeRestart("muffleWarning")
    })
  fe <- nzchar(pkgDT$localFiles)
  if (any(fe)) {
    pkgDT[fe, localRepo := DESCRIPTIONFileOtherV(localFiles, "RemoteRepo")]
    pkgDT[fe, localUsername := DESCRIPTIONFileOtherV(localFiles, "RemoteUsername")]
    pkgDT[fe, localBranch := DESCRIPTIONFileOtherV(localFiles, "RemoteRef")]
    pkgDT[fe, installedSha := DESCRIPTIONFileOtherV(localFiles, "RemoteSha")]
  }
  if (any(!fe)) {
    pkgDT[!fe, localRepo := NA]
    pkgDT[!fe, localUsername := NA]
    pkgDT[!fe, localBranch := NA]
    pkgDT[!fe, installedSha := NA]
  }

  pkgDT <- installedVers(pkgDT, libPaths = libPaths)

  if (isTRUE(any(pkgDT$installed %in% TRUE))) {
    brOrSha <- pkgDT$Branch == pkgDT$localBranch |
      pkgDT$Branch == pkgDT$installedSha

    installedNotOK <- pkgDT$Account != pkgDT$localUsername |
      pkgDT$Repo != pkgDT$localRepo |
      brOrSha %in% FALSE

  } else {
    installedNotOK <- NA
  }
  set(pkgDT, NULL, "installedVersionOK", installedNotOK %in% FALSE)
  pkgDT
}


toPkgDepDT <- function(packageFullName, neededFromDESCRIPTION, pkg, verbose) {
  if (missing(pkg))
    pkg <- packageFullName

  pkgDepDT <- toPkgDT(packageFullName)
  if (missing(neededFromDESCRIPTION)) {
    neededFromDESCRIPTION <- pkgDepDT$packageFullName
  }
  if (NROW(pkgDepDT)) {
    pkgDepDT <- parsePackageFullname(pkgDepDT, sorted = FALSE)
    pkgDepDT <- parseGitHub(pkgDepDT)
    set(pkgDepDT, NULL, "isGitPkg", !is.na(pkgDepDT$githubPkgName))
    # pkgDepDT[, isGitPkg := !is.na(pkgDepDT$githubPkgName)]
    # pkgDepDT <- trimRedundancies(pkgDepDT, repos = repos, purge = FALSE)
    setorderv(pkgDepDT, "isGitPkg", order = -1)
    # pkgDepDT[, Package := extractPkgName(packageFullName)]
    # set(pkgDepDT, NULL, "versionSpec", extractVersionNumber(packageFullName))

    # Here, GitHub package specification in a DESCRIPTION file Remotes section
    #   won't have version numbering --> Need to merge the two fields



    # This shouldn't be "Version" but "versionSpec" from here
    if (any(!is.na(pkgDepDT[["versionSpec"]]))) {
      whHasVersion <- which(!is.na(pkgDepDT[["versionSpec"]]))
      set(pkgDepDT, whHasVersion, "inequality",
          extractInequality(pkgDepDT$packageFullName[whHasVersion]))
      set(pkgDepDT, NULL, "github", extractPkgGitHub(pkgDepDT$packageFullName))
      if (any(pkgDepDT$isGitPkg == TRUE &
              !is.na(pkgDepDT[["versionSpec"]]))) {
        pkgDepDT[isGitPkg == TRUE & !is.na(versionSpec), newPackageFullName :=
                   ifelse(
                     is.na(extractVersionNumber(packageFullName)),
                     paste0(packageFullName, " (", inequality, versionSpec, ")"),
                     NA
                   )]
        whGitNeedVersion <- !is.na(pkgDepDT$newPackageFullName)
        if (any(whGitNeedVersion)) {
          pkgDepDT[whGitNeedVersion == TRUE, packageFullName := newPackageFullName]
        }
      }
    }
    dup <- duplicated(pkgDepDT, by = c("Package", "versionSpec"))
    pkgDepDT <- pkgDepDT[dup == FALSE]
    differences <-
      setdiff(setdiff(pkgDepDT$Package, .basePkgs), extractPkgName(neededFromDESCRIPTION))
    if (length(differences)) {
      messageVerbose(
        " (-- The DESCRIPTION file for ",
        pkg,
        " is incomplete; there are missing imports: ",
        paste(differences, collapse = comma),
        " --) ",
        verbose = verbose,
        verboseLevel = 1
      )
    }
  }
  pkgDepDT
}


getAvailablePackagesCheckAdditRepos <- function(pkgDepDTList2, pkgDepDT, repos, verbose, type, ap = NULL) {
  #
  anyNewAdditionalRepositories <-
    unlist(lapply(pkgDepDTList2, function(dt)
      if (is.null(dt$Additional_repositories)) NULL else unique(dt$Additional_repositories)))
  if (!is.null(anyNewAdditionalRepositories) || is.null(ap)) {


    isDT <- vapply(pkgDepDTList2, is.data.table, FUN.VALUE = logical(1))
    if (any(isDT)) {
      if (any(!isDT))
        pkgDepDTList2 <- pkgDepDTList2[isDT]
      if (missing(pkgDepDT))
        pkgDepDT <- rbindlist(pkgDepDTList2, fill = TRUE, use.names = TRUE)
      additionalRepos <- lapply(pkgDepDTList2, function(DT)
        if (!is.null(DT$Additional_repositories)) unique(DT$Additional_repositories) else NULL)
      uniqueAdditionalRepos <- vapply(additionalRepos, function(ar) paste(sort(ar), collapse = "__"), FUN.VALUE = character(1))
      uniques <- as.integer(factor(paste0(seq(uniqueAdditionalRepos), uniqueAdditionalRepos)))
      hasAdditionalRepos <- vapply(additionalRepos, function(ar) NROW(ar) > 0, FUN.VALUE = logical(1))
      if (sum(hasAdditionalRepos)) {
        ap <- lapply(uniques, function(un) {
          DT <- rbindlist(pkgDepDTList2[un], fill = TRUE, use.names = TRUE)
          repos <- c(repos, na.omit(unique(DT$Additional_repositories)))
          needPurge <- FALSE
          for (i in 1:2) {
            ap <-
              getAvailablePackagesIfNeeded(DT, repos, purge = needPurge, verbose, type)
            if (!is.null(ap)) {
              repo1 <- noHttp(repos)
              haveRepos <- unlist(lapply(repo1, function(re) any(startsWith(noHttp(unique(ap$Repository)), re))))
              needPurge <- !all(haveRepos)
            }
            if (needPurge %in% FALSE)
              break
          }
          ap
        })
        ap <- unique(rbindlist(ap, use.names = TRUE, fill = TRUE))
      } else {
        if (is.null(ap))
          ap <-
            getAvailablePackagesIfNeeded(pkgDepDT, repos, purge = FALSE, verbose, type)
      }
    }
  }
  ap
}



getArchiveDetailsInnerMemoise <- function(...) {
  if (getOption("Require.useMemoise", TRUE)) {
    dots <- list(...)
    if (is.null(envPkgDepArchiveDetailsInner())) {
      envPkgDepArchiveDetailsInnerCreate()
    }
    ret <- NULL
    ss <- match.call(definition = getArchiveDetailsInner)
    Package <- eval(ss$Package, envir = parent.frame())
    if (!exists(Package, envir = envPkgDepArchiveDetailsInner(), inherits = FALSE)) {
      assign(Package, list(), envir = envPkgDepArchiveDetailsInner())
      # envPkgDepArchiveDetailsInner()[[Package]] <- list()
    } else {
      if (length(envPkgDepArchiveDetailsInner()[[Package]]) > 0) {
        # This is trigger
        prevInOuts <- envPkgDepArchiveDetailsInner()[[Package]][[2]]
        whIdent <- identical(prevInOuts$input, dots[[2]])
        if (any(whIdent)) {
          ret <- prevInOuts$output
        }
      }

    }
    if (is.null(ret)) {
      # inputs <- dots[[2]]
      inputs <- data.table::copy(dots[[2]])  # just take ava argument -- it has everything that is relevant
      ret <- getArchiveDetailsInner(...)
      assign(Package,
             list(envPkgDepArchiveDetailsInner()[[Package]], list(input = inputs, output = ret)),
             envir = envPkgDepArchiveDetailsInner())
      # envPkgDepArchiveDetailsInner()[[Package]] <-
      #   list(envPkgDepArchiveDetailsInner()[[Package]], list(input = inputs, output = ret))
    }
  } else {
    ret <- getArchiveDetailsInner(...)
  }

  return(ret)
}

noHttp <- function(url) {
  gsub("^http.*://", "", url)
}


getVersionOptionPkgEnv <- function(psnNoVersion, verNum, inequ) {
  pat <- paste0(psnNoVersion)
  pat <- gsub("[[:punct:]]| ", "_", pat)
  nams <- names(as.list(envPkgDepDeps()))
  if (!is.null(nams)) {
    versions <- Map(pa = pat, ver = verNum, ineq = inequ, #MoreArgs = list(nam = nams),
                function(pa, ver, ineq) {
                  sw <- startsWith(prefix = pa, nams)
                  if (any(sw)) {
                    # if (all(sw %in% FALSE))
                    #   sw <- grep(pa, nams)
                    nams <- nams[sw]
                    # nams <- nams[have]

                    poss <- get0(nams, envir = envPkgDepDeps())
                    versions <- extractVersionNumber(names(poss))
                    # have <- ls(envPkgDepDeps(), pattern = pat) # TOO SLOW APPARENTLY
                    # versions <- gsub(paste0("^", pa, "\\_\\_*([[:alnum:]]{40})|[(.+)\\_\\_+].+$"), "\\1", nams)
                    if (!is.null(ineq)) {
                      if (!is.na(ineq)) {
                        if (length(versions) > 0) {
                          okVers <- compareVersion2(gsub("_", "\\.", versions),
                                                    versionSpec = ver, inequality = ineq)
                          if (any(!okVers)) {
                            versions[!okVers] <- ""
                          }
                        }
                      }
                    }
                    noSavedOption <- nchar(versions) == 0
                    if (!is.null(ver))
                      if (any(noSavedOption))
                        versions[noSavedOption] <- verNum[noSavedOption]
                    if (length(versions) > 1)
                      versions <- tail(versions, 1)
                    versions
                  } else {
                    NULL
                  }
                })
  } else {
    versions <- NULL
  }

  versions

}




#' @description
#' `pkgDep2` is a convenience wrapper of `pkgDep` that "goes one level in",
#' i.e., the first order dependencies, and runs the `pkgDep` on those.
#' @rdname pkgDep
#' @export
#' @examples
#' \donttest{
#' if (Require:::.runLongExamples()) {
#'   opts <- Require:::.setupExample()
#'
#'   pkgDep2("reproducible")
#'   # much bigger one
#'   pkgDep2("tidyverse")
#'
#'   Require:::.cleanup(opts)
#' }
#' }
pkgDep2 <- function(...) {
  dots <- list(...)
  dots$recursive <- FALSE
  forms <- formals(pkgDep)
  if (is.null(dots$simplify)) dots$simplify <- forms$simplify
  # dots$simplify <- FALSE
  deps <- do.call(pkgDep, dots)
  dots[[1]] <- NULL
  dots$recursive <- TRUE
  if (isTRUE(dots$simplify %in% FALSE)) {
    deps1 <- lapply(deps$deps, function(dep) do.call(pkgDep, append(list(dep$packageFullName), dots)))
  } else {
    deps1 <- lapply(deps, function(dep) do.call(pkgDep, append(list(dep), dots)))
  }
  deps1
}




purgeAvailablePackages <- function(repos, purge = FALSE) {
  if (isTRUE(purge)) {
    fn <- availablePackagesCachedPath(repos = repos, type = c("source", "binary"))
    fExists <- file.exists(fn)
    if (any(fExists)) {
      unlink(fn[fExists])
    }
  }
  purge
}
