#' Download data from Natural Earth and (optionally) read into R
#'
#' returns downloaded data as a spatial object or the filename if
#' \code{load=FALSE}. if \code{destdir} is specified the data can be reloaded in
#' a later R session using \code{\link{ne_load}} with the same arguments.
#'
#' @param scale The scale of map to return, one of `110`, `50`, `10` or `small`,
#' `medium`, `large`.
#'
#' @param type type of natural earth file to download one of 'countries',
#' 'map_units', 'map_subunits', 'sovereignty', 'states' OR the portion of any
#' natural earth vector url after the scale and before the . e.g. for
#' 'ne_50m_urban_areas.zip' this would be 'urban_areas'. See Details. OR the
#' raster filename e.g. for 'MSR_50M.zip' this would be 'MSR_50M'
#'
#' @param category one of natural earth categories : 'cultural', 'physical',
#' 'raster'
#'
#' @param destdir where to save files, defaults to \code{tempdir()},
#' \code{getwd()} is also possible.
#'
#' @param load `TRUE` to load the spatial object into R, `FALSE` to return the
#' filename of the downloaded object. If the requested object is a vector, it
#' will be saved as a GPKG file. If a raster is requested, it will be saved as
#' a GeoTIFF file.
#'
#' @details Note that the filename of the requested object will be returned if
#' `load = FALSE`.
#'
#' @details
#' **Caching behavior:** When `load = TRUE` and `destdir` is set to a persistent
#' directory (not `tempdir()`), the function will check if the file already
#' exists locally. If it does, it will load from the cached file instead of
#' re-downloading. If the file doesn't exist, it will download from Natural Earth,
#' save a copy to `destdir`, and then load it into R. This allows you to use the
#' same code whether it's the first download or a subsequent load, avoiding
#' redundant downloads.
#'
#' @details
#' If `load = TRUE` and `destdir = tempdir()` (the default), the download will
#' be handled using the GDAL virtual file system, allowing direct access to the
#' data without writing it to disk.
#'
#' @seealso \code{\link{ne_load}}, pre-downloaded data are available using
#'   \code{\link{ne_countries}}, \code{\link{ne_states}}. Other geographic data
#'   are available in the raster package : \code{\link[raster]{getData}}.
#'
#' @param returnclass A string determining the spatial object to return. Either
#' "sf" for for simple feature (from `sf`, the default) or "sv" for a
#' `SpatVector` (from `terra`).
#'
#' @return An object of class `sf` for simple feature (from `sf`, the default)
#' or `SpatVector` (from `terra`).
#'
#' @examples \dontrun{
#' # Download and load into R (default behavior, no local copy saved)
#' spdf_world <- ne_download(scale = 110, type = "countries")
#' plot(spdf_world)
#'
#' # Download with caching: first call downloads and saves, subsequent calls load from cache
#' # First call: downloads from Natural Earth and saves to "my_maps" directory
#' spdf_world <- ne_download(scale = 110, type = "countries", destdir = "my_maps")
#'
#' # Second call: loads from cached file in "my_maps" (no re-download)
#' spdf_world2 <- ne_download(scale = 110, type = "countries", destdir = "my_maps")
#'
#' # This works the same across R sessions - same code, caches automatically
#'
#' # You can also use ne_load() to explicitly load from cache
#' spdf_world3 <- ne_load(scale = 110, type = "countries", destdir = "my_maps")
#'
#' # Download and save without loading into R
#' file_path <- ne_download(
#'   scale = 110, type = "countries",
#'   destdir = "my_maps", load = FALSE
#' )
#'
#' # Raster example with caching
#' rst <- ne_download(
#'   scale = 50, type = "MSR_50M",
#'   category = "raster", destdir = "my_maps"
#' )
#' # Subsequent calls will use cached version
#' rst2 <- ne_download(
#'   scale = 50, type = "MSR_50M",
#'   category = "raster", destdir = "my_maps"
#' )
#'
#' library(terra)
#' terra::plot(rst)
#' # end dontrun
#' }
#'
#' @export
ne_download <- function(
  scale = 110L,
  type = "countries",
  category = c("cultural", "physical", "raster"),
  destdir = tempdir(),
  load = TRUE,
  returnclass = c("sf", "sv")
) {
  category <- match.arg(category)
  returnclass <- match.arg(returnclass)

  if (!dir.exists(destdir)) {
    cli::cli_abort("{.arg destdir} must be an existing directory")
  }

  if (returnclass == "sp") {
    deprecate_sp("ne_download(returnclass = 'sp')")
  }

  scale <- check_scale(scale)

  gdal_url <- ne_file_name(
    scale = scale,
    type = type,
    category = category
  )

  # Check if we should use cached file
  # When load = TRUE and destdir is not tempdir, use caching behavior
  use_cache <- load && !identical(destdir, tempdir())

  if (use_cache) {
    spatial_file_path <- make_dest_path(gdal_url, category, destdir)

    # If file exists in cache, load it
    if (file.exists(spatial_file_path)) {
      cli::cli_inform(
        "Loading {.file {basename(spatial_file_path)}} from cache..."
      )

      if (category == "raster") {
        return(terra::rast(spatial_file_path))
      } else {
        layer <- layer_name(type, scale)
        return(read_spatial_vector(
          spatial_file_path,
          layer = layer,
          returnclass = returnclass
        ))
      }
    }
  }

  cli::cli_inform("Reading {.file {basename(gdal_url)}} from naturalearth...")

  if (category == "raster") {
    # Try without subfolder first (TYPE.zip/TYPE.tif), then with subfolder
    # (TYPE.zip/TYPE/TYPE.tif) since Natural Earth zips have inconsistent
    # structure
    spatial_object <- tryCatch(
      suppressWarnings(terra::rast(gdal_url)),
      error = function(e) {
        type_name <- tools::file_path_sans_ext(basename(gdal_url))
        gdal_url_with_folder <- file.path(
          dirname(gdal_url),
          type_name,
          basename(gdal_url)
        )
        terra::rast(gdal_url_with_folder)
      }
    )
  } else {
    layer <- layer_name(type, scale)
    spatial_object <- read_spatial_vector(
      gdal_url,
      layer = layer,
      returnclass = returnclass
    )
  }

  if (load) {
    # If using cache directory, save a copy for future use
    if (use_cache) {
      spatial_file_path <- make_dest_path(gdal_url, category, destdir)

      cli::cli_inform(
        "Writing {.file {basename(spatial_file_path)}} to {.path {destdir}}..."
      )

      if (category == "raster") {
        terra::writeRaster(
          spatial_object,
          spatial_file_path,
          overwrite = TRUE,
          gdal = c(
            "COMPRESS=ZSTD",
            "PREDICTOR=2",
            "TILED=YES",
            "TFW=NO",
            "BLOCKXSIZE=256",
            "BLOCKYSIZE=256"
          )
        )
      } else if (returnclass == "sf") {
        sf::write_sf(spatial_object, spatial_file_path, delete_dsn = TRUE)
      } else {
        terra::writeVector(spatial_object, spatial_file_path, overwrite = TRUE)
      }
    }

    return(spatial_object)
  }

  spatial_file_path <- make_dest_path(gdal_url, category, destdir)

  cli::cli_inform(
    "Writing {.file {basename(spatial_file_path)}} to {.path {destdir}}..."
  )

  if (category == "raster") {
    terra::writeRaster(
      spatial_object,
      spatial_file_path,
      overwrite = TRUE,
      gdal = c(
        "COMPRESS=ZSTD",
        "PREDICTOR=2",
        "TILED=YES",
        "TFW=NO",
        "BLOCKXSIZE=256",
        "BLOCKYSIZE=256"
      )
    )
  } else if (returnclass == "sf") {
    sf::write_sf(spatial_object, spatial_file_path, delete_dsn = TRUE)
  } else {
    terra::writeVector(spatial_object, spatial_file_path, overwrite = TRUE)
  }

  invisible(spatial_file_path)
}
