#' Get MSigDB geneset and geneset_name
#'
#' Geneset is a data.frame of 2 columns with term id and gene id.
#' The function keeps the original public interface and automatically
#' distinguishes Human MSigDB collections from native Mouse MSigDB
#' collections by `category`.
#'
#' Human MSigDB collections (e.g. H, C1, C2-CP-REACTOME, C5-GO-BP) may contain ortholog-converted
#' data for species supported by `msigdb_org`.
#'
#' Native Mouse MSigDB collections (e.g. MH, M1, M2-CP-REACTOME,
#' M5-GO-BP) are only support Mus musculus.
#'
#' @param org Organism name from `msigdb_org`.
#' @param category MSigDB collection/subcollection name. Existing Human
#'   collection names remain supported; native Mouse collection names such
#'   as "MH", "M1", "M2-CP-REACTOME" and "M5-GO-BP" are also supported.
#' @param download.method "auto" (as default if NULL), "wininet" (for windows)
#' @param data_dir data saving location, default is the package data directory
#' @importFrom dplyr %>% filter pull
#'
#' @return A list including geneset.
#' @export
#' @examples
#' \donttest{
#' # Human native Hallmark
#' x <- getMsigdb(org = "human", category = "H", data_dir = tempdir())
#'
#' # Human MSigDB converted to mouse orthologs
#' x <- getMsigdb(org = "mouse", category = "H", data_dir = tempdir())
#'
#' # Native Mouse MSigDB
#' x <- getMsigdb(org = "mouse", category = "MH", data_dir = tempdir())
#' }
getMsigdb <- function(org = 'human',
                      category = c("H", "C1", "C2-CGP", "C2-CP-BIOCARTA", "C2-CP-KEGG", "C2-CP-PID",
                                   "C2-CP-REACTOME", "C2-CP-WIKIPATHWAYS", "C3-MIR-MIRDB",
                                   "C3-MIR-MIR_Legacy", "C3-TFT-GTRD", "C3-TFT-TFT_Legacy",
                                   "C4-CGN", "C4-CM", "C5-GO-BP", "C5-GO-CC", "C5-GO-MF",
                                   "C5-HPO", "C6", "C7-IMMUNESIGDB", "C7-VAX", "C8"),
                      download.method = NULL,
                      data_dir = NULL) {

  #------------------------------#
  # 1. Validate arguments
  #------------------------------#
  # Keep the historical default behavior: if category is omitted, use "H".
  # Do not use match.arg() here, because 04-msigdb_data.R discovers MSigDB
  # collections dynamically and newer HS/MM collections should work without
  # requiring this function to be edited every time MSigDB changes.
  if (missing(category)) {
    category <- "H"
  }

  if (length(category) != 1L || is.na(category) || !nzchar(trimws(category))) {
    stop("`category` must be one non-empty MSigDB collection name.", call. = FALSE)
  }

  category <- trimws(as.character(category))
  category <- gsub(":", "-", category, fixed = TRUE)

  # Do not allow a category to escape the expected server/cache directory.
  if (grepl("[/\\\\]", category) || grepl("..", category, fixed = TRUE)) {
    stop("Invalid `category`: path separators and '..' are not allowed.", call. = FALSE)
  }

  org <- map_msigdb_org(org)

  # Native Mouse MSigDB collection names start with MH or M<digit>,
  # e.g. MH, M1, M2-CGP, M2-CP-REACTOME, M5-GO-BP, M7, M8.
  # This deliberately does NOT classify strings such as MIRDB as Mouse DB.
  is_mouse_db <- grepl("^M(H|[0-9]+)($|-)", category)

  if (is_mouse_db && !identical(org, "Mus_musculus")) {
    stop(
      "MSigDB category '", category,
      "' belongs to the native Mouse MSigDB and only supports Mus musculus.\n",
      "Please use `org = \"mouse\"` (or `org = \"Mus musculus\"`).",
      call. = FALSE
    )
  }

  #------------------------------#
  # 2. Build remote/local paths
  #------------------------------#
  if (is.null(data_dir)) {
    data_dir <- tools::R_user_dir("geneset", which = "data")
  }

  # Server layout generated by 04-msigdb_data.R:
  #   HS: /anno/msigdb/<category>/<species>_geneset.fst
  #   MM: /anno/msigdb/MM/<category>/Mus_musculus_geneset.fst
  remote_root <- "/anno/msigdb"
  sub_dir <- if (is_mouse_db) {
    paste0(remote_root, "/MM/", category, "/")
  } else {
    paste0(remote_root, "/", category, "/")
  }

  filename <- paste0(org, "_geneset.fst")
  url <- paste0(web.url(), sub_dir, filename)

  # Separate each category in the local cache. This fixes the previous
  # collision where H, C5-GO-BP, etc. all used the same local filename.
  cache_dir <- if (is_mouse_db) {
    file.path(data_dir, "anno", "msigdb", "MM", category)
  } else {
    file.path(data_dir, "anno", "msigdb", category)
  }
  make_dir(cache_dir)

  destfile <- file.path(cache_dir, filename)

  #------------------------------#
  # 3. Download/update cache
  #------------------------------#
  web_f_size <- check_web_size(url)
  local_f_size <- if (file.exists(destfile)) {
    as.numeric(file.info(destfile)$size)
  } else {
    0
  }

  download_ok <- geneset_download(
    url = url,
    destfile = destfile,
    data_dir = cache_dir,
    method = download.method,
    web_f_size = web_f_size,
    local_f_size = local_f_size
  )

  # If the update failed but a valid previous cache exists, geneset_download()
  # deliberately keeps that cache. Only stop when no readable local file exists.
  if (!file.exists(destfile) ||
      is.na(file.info(destfile)$size) ||
      file.info(destfile)$size <= 0) {
    stop(
      "MSigDB data file is unavailable after the download attempt.\n",
      "URL: ", url, "\n",
      "Expected local file: ", destfile,
      call. = FALSE
    )
  }

  #------------------------------#
  # 4. Read geneset
  #------------------------------#
  geneset <- tryCatch(
    suppressWarnings(fst::read.fst(destfile)),
    error = function(e) {
      stop(
        "Failed to read MSigDB file: ", destfile, "\n",
        "Reason: ", conditionMessage(e),
        call. = FALSE
      )
    }
  )

  required_cols <- c("gs_name", "entrez_gene")
  missing_cols <- setdiff(required_cols, colnames(geneset))
  if (length(missing_cols) > 0L) {
    stop(
      "Invalid MSigDB file structure. Missing column(s): ",
      paste(missing_cols, collapse = ", "),
      call. = FALSE
    )
  }

  #------------------------------#
  # 5. Keep the original return structure
  #------------------------------#
  res <- list()
  res[["geneset"]] <- geneset
  res[["geneset_name"]] <- NA

  # Add organism for other use, preserving the original output behavior.
  ensOrg_name <- ensOrg_name_data()
  rm(ensOrg_name, envir = .genesetEnv)

  org2 <- gsub("_", " ", org, fixed = TRUE)
  add_org <- ensOrg_name %>%
    dplyr::filter(tolower(latin_full_name) == tolower(org2)) %>%
    dplyr::pull(latin_short_name)

  if (length(add_org) == 0L) {
    add_org <- NA
  } else {
    add_org <- add_org[[1L]]
  }

  res$organism <- add_org
  res$type <- "msigdb"

  invisible(res)
}

utils::globalVariables(c("msigdb_org", "latin_short_name"))
