
PREPS <- list()
CHECKS <- list()

#' List the names of all checks
#'
#' @return Character vector of checks
#' @family check_groups
#' @export
#' @examples
#' all_checks()

all_checks <- function() {
  names(CHECKS)
}

#' List the names of default checks (excludes optional check sets)
#'
#' @return Character vector of default check names
#' @family check_groups
#' @export
#' @examples
#' default_checks()

default_checks <- function() {
  setdiff(all_checks(), tidyverse_checks())
}

#' List the names of tidyverse style checks
#'
#' These checks are optional and not included in the default set.
#' They are powered by \code{\link[lintr]{lint_package}} using lintr's
#' default linter set and respect any \code{.lintr} configuration file
#' in the package root (e.g. to disable specific linters or add exclusions).
#' Add them via \code{checks = c(default_checks(), tidyverse_checks())}.
#'
#' @return Character vector of tidyverse check names
#' @family check_groups
#' @export
#' @examples
#' tidyverse_checks()

tidyverse_checks <- function() {
  grep("^tidyverse_", all_checks(), value = TRUE)
}

#' Describe one or more checks
#'
#' @param check_name Names of checks to be described.
#' @return List of character descriptions for each \code{check_name}
#' @family check_groups
#' @export
#' @examples 
#' describe_check("rcmdcheck_non_portable_makevars")
#' check_name <- c("no_description_depends",
#'                 "lintr_assignment_linter",
#'                 "no_import_package_as_a_whole",
#'                 "rcmdcheck_missing_docs")
#' describe_check(check_name)
#' # Or to see all checks:
#' \dontrun{
#'   describe_check(all_checks())
#' }

describe_check <- function(check_name = NULL) {
  check_name <- intersect(check_name, names(CHECKS))
  lapply(CHECKS[check_name], function(i) i$description)
}

#' List available check group names
#'
#' Returns the names of all registered check groups.
#' Use these names with [checks_by_group()] to select checks by group,
#' or with \code{options(goodpractice.exclude_check_groups = ...)} to skip
#' groups.
#' Full descriptions of each check group are return by
#' [describe_check_groups()].
#'
#' @return A character vector of check group names.
#' @family check_groups
#' @export
#' @examples
#' # Names of all check groups:
#' all_check_groups()
#'
#' # List individual checks by group:
#' chks <- lapply(all_check_groups(), checks_by_group)
#' names(chks) <- all_check_groups()
#' chks

all_check_groups <- function() {
  names(PREPS)
}

#' Describe available check groups
#'
#' Returns full descriptions of all registered check groups.
#'
#' @return A named list of each check group defined in [all_check_groups()],
#' with text descriptions of each group.
#' @family check_groups
#' @export
#' @examples
#' # Names of all check groups:
#' all_check_groups()
#'
#' # And corresponding descriptions:
#' describe_check_groups()

describe_check_groups <- function() {
  num_linters <- length(grep("linter", all_checks()))
  d <- list(
    covr = "Test coverage report from 'covr' package.",
    cyclocomp = paste0(
      "Function cyclocomplexity with the 'cyclocomp' ",
      "package; default limit of 50."
    ),
    description = paste0(
      "Check common issues with DESCRIPTION files, including ",
      "formatting issues with URLs, DOIs, BugReports, package ",
      "names, and author and contributor roles"
    ),
    lintr = paste0(
      "Check package linting with the 'lintr' package (",
      num_linters,
      " linters in total)."
    ),
    namespace = "Check import and export patterns in NAMESPACE file",
    rcmdcheck = paste0(
      "Run 'R CMD check' via the 'rcmdcheck' package. ~200 checks for ",
      "documentation, namespace, compilation, tests, vignettes, ",
      "CRAN compliance."
    ),
    rd = paste0(
      "Check whether or not 'man/*.Rd' function documentation includes ",
      "both example code and return values (regardless of whether or not ",
      "documentation files are generated by 'Roxygen2')."
    ),
    revdep = paste0(
      "Check whether package has reverse dependencies, and ",
      "recommending running 'reddev' package if so."
    ),
    roxygen2 = paste0(
      "Only for packages within use 'Roxygen2' to generate documentation. ",
      "Checks for best practices in Roxygen2 tag usage, flags any ",
      "unknown tags, and ensures 'inheritParams' is used correctly."
    ),
    code_structure = paste0(
      "Common issues like duplicated or unused function bodies, ",
      "that 'print()' returns insivibly, that 'on.exit()' uses ",
      "'add = TRUE', and checks on function length (default max",
      "50 lines)."
    ),
    package_structure = paste0(
      "Generic checks like whether a package has a README, a NEWS file, ",
      "or whether all files use a '.R' extension, and not '.r'."
    ),
    spelling = "Check spelling with the 'spelling' package.",
    tidyverse = paste0(
      "Check compliance with the Tidyverse style guide; mostly via 'lintr' package. ",
      "(These checks are not run by default; and only if 'tidyverse_checks()' are ",
      "added to 'checks_by_group()'."
    ),
    urlchecker = paste0(
      "Check whether all URLs are valid, including",
      "identifying any redirects."
    ),
    vignette = "Check that vignette code does not use either 'rm()' or 'setwd()'."
  )
  if (!identical(names(d), all_check_groups())) {
    cli::cli_abort("Group names in 'describe_check_groups' differ from 'all_check_groups'.")
  }
  d
}

#' Select checks by check group
#'
#' Returns the names of all checks that belong to the given group(s).
#' This makes it easy to run or inspect a specific category of checks
#' without knowing individual check names.
#'
#' @param ... Group names as character strings. Use [all_check_groups()] to
#'   see available names.
#' @return Character vector of check names
#' @family check_groups
#' @export
#' @examples
#' # run only DESCRIPTION and namespace checks
#' checks_by_group("description", "namespace")
#'
#' # see what the lintr group covers
#' checks_by_group("lintr")
#' # See all checks by group:
#' lapply(all_check_groups(), checks_by_group)
#' # use directly in gp()
#' \dontrun{
#'   gp(".", checks = checks_by_group("description", "lintr"))
#' }

checks_by_group <- function(...) {
  group <- c(...)
  if (length(group) == 0) return(character(0))
  unknown <- setdiff(group, names(PREPS))
  if (length(unknown) > 0) {
    cli::cli_warn(c(
      "Unknown check group{?s}: {.val {unknown}}.",
      i = "Use {.fn all_check_groups} to see available groups."
    ))
  }
  names(Filter(function(ch) any(ch$preps %in% group), CHECKS))
}
