# download_wid() + query builder

new_wid_df <- function(df) structure(df, class = c("wid_df", "data.frame"))

#' Print a wid_df object
#'
#' This method prints a summary of a \code{wid_df} object,
#' including the number of rows, countries, and variables.
#'
#' @param x A \code{wid_df} object.
#' @param ... Additional arguments passed to \code{print()}.
#' @return Invisibly returns the input \code{x}.
#' @examples
#' \donttest{
#' d <- data.frame(country = "US", variable = "sptinc992j",
#'                 percentile = "p99p100", year = "2020",
#'                 value = 0.19, age = "992", pop = "j",
#'                 stringsAsFactors = FALSE)
#' class(d) <- c("wid_df", "data.frame")
#' print(d)
#' }
#' @export
print.wid_df <- function(x, ...) {
  cat(sprintf("<wid_df>  %d rows | %d countries | %d variables\n",
              nrow(x), length(unique(x$country)), length(unique(x$variable))))
  NextMethod()
  invisible(x)
}

# Stub target: does catalogue lookup + filtering + data fetch; returns plain df of rows.
.fetch <- function(indicators, areas, perc, ages, pop, no_extrapolation, verbose) {
  if (verbose) message("Fetching variable catalogue...")
  variables <- do.call(rbind, lapply(
    if (identical(indicators, "all")) "all" else indicators,
    .get_variables_areas,
    areas = if (identical(areas, "all")) "all" else areas))
  
  if (is.null(variables) || nrow(variables) == 0L) {
    warning("No data found for this selection.", call. = FALSE)
    return(NULL)
  }
  variables <- .filter_vars(variables, perc, ages, pop)
  if (nrow(variables) == 0L) {
    warning("No data after applying filters.", call. = FALSE)
    return(NULL)
  }
  
  variables$api_code <- paste(variables$variable, variables$percentile,
                              variables$age, variables$pop, sep = "_")
  if (verbose) message(sprintf("Fetching %d variable(s)...", length(unique(variables$variable))))
  
  data <- .chunk_fetch(variables, size = 10L, function(countries, codes)
    .get_data_variables(countries, codes, no_extrapolation = no_extrapolation))
  
  if (is.null(data) || nrow(data) == 0L) {
    warning("API returned no data.", call. = FALSE)
    return(NULL)
  }
  
  data <- .parse_indicator(data)
  data <- merge(data, unique(variables[, c("variable","percentile","age","pop")]),
                by = c("variable","percentile"), all.x = TRUE)
  data <- data[!duplicated(data[, c("country","variable","percentile","year")]), ]
  data[order(data$country, data$variable, data$percentile, data$year), ]
}

.fetch_meta <- function(variables)
  .chunk_fetch(variables, size = 10L, function(countries, codes)
    .get_metadata_variables(countries, codes))

#' Download data from WID.world
#'
#' Returns a \code{wid_df}, a classed \code{data.frame} that works natively
#' with dplyr, ggplot2, and all base-R operations.
#'
#' @param indicators WID variable codes or \code{"all"}.
#' @param areas      ISO-2 area codes or \code{"all"}.
#' @param years      Integer vector or \code{"all"}.
#' @param perc       Percentile codes or \code{"all"}.
#' @param ages       Age codes or \code{"all"}. Default \code{"992"}.
#' @param pop        Population unit codes or \code{"all"}. Default \code{"j"}.
#' @param metadata   Logical. If \code{TRUE}, fetches metadata as a
#'   \code{"wid_meta"} attribute on the returned object. Default \code{FALSE}.
#' @param include_extrapolations Logical. Default \code{TRUE}.
#' @param verbose    Logical. Default \code{FALSE}.
#' @param cache      Logical. Default \code{TRUE}.
#' @return A \code{wid_df} (a classed \code{data.frame}) with columns
#'   \code{country}, \code{variable}, \code{percentile}, \code{year},
#'   \code{value}, \code{age}, \code{pop}. If \code{metadata = TRUE},
#'   a \code{"wid_meta"} attribute is attached.
#' @examples
#' \donttest{
#'   if (nzchar(Sys.getenv("WID_API_KEY"))) {
#'     d <- download_wid(
#'       indicators = "sptinc992j",
#'       areas      = "US",
#'       perc       = "p99p100",
#'       years      = 2010:2020
#'     )
#'   }
#' }
#' @export
download_wid <- function(indicators = "all", areas = "all", years = "all",
                         perc = "all", ages = "992", pop = "j",
                         metadata = FALSE, include_extrapolations = TRUE,
                         verbose = FALSE, cache = TRUE) {
  if (identical(indicators, "all") && identical(areas, "all"))
    stop("Specify at least one of `indicators` or `areas`.", call. = FALSE)
  
  if (!identical(indicators, "all")) indicators <- as.character(indicators)
  if (!identical(areas,  "all")) areas <- wid_validate(areas = as.character(areas))$areas
  if (!identical(years,  "all")) years <- wid_validate(years = years)$years
  if (!identical(perc,   "all")) perc  <- wid_validate(perc  = as.character(perc))$perc
  if (!identical(ages,   "all"))
    ages <- wid_validate(age = formatC(suppressWarnings(as.integer(ages)),
                                       width = 3L, flag = "0"))$age
  if (!identical(pop,    "all")) pop <- wid_validate(pop = as.character(pop))$pop
  
  key <- .cache_key(indicators, areas, years, perc, ages, pop, metadata, include_extrapolations)
  if (cache) { hit <- .cache("get", key); if (!is.null(hit)) return(hit) }
  
  if (verbose) message("Fetching data from WID.world...")
  
  rows <- .fetch(indicators, areas, perc, ages, pop,
                 no_extrapolation = !include_extrapolations, verbose = verbose)
  
  # If stub returned a wid_df directly, pass through unchanged
  if (inherits(rows, "wid_df")) rows <- as.data.frame(rows)
  
  if (is.null(rows)) {
    empty <- new_wid_df(
      .empty("country", "variable", "percentile", "year", "value", "age", "pop"))
    if (cache) .cache("set", key, empty)
    return(invisible(empty))
  }
  
  if (!identical(years, "all"))
    rows <- rows[as.integer(rows$year) %in% as.integer(years), ]
  
  rownames(rows) <- NULL
  result <- new_wid_df(rows)
  
  if (metadata) {
    vars <- do.call(rbind, lapply(
      if (identical(indicators, "all")) "all" else indicators,
      .get_variables_areas,
      areas = if (identical(areas, "all")) "all" else areas))
    meta <- if (!is.null(vars) && nrow(vars)) .fetch_meta(vars) else data.frame()
    attr(result, "wid_meta") <- meta
  }
  
  if (cache) .cache("set", key, result)
  result
}

#' @rdname download_wid
#' @export
download <- download_wid

#' Build a reusable WID query object
#'
#' Creates a \code{wid_query} object that stores query parameters for WID data fetching.
#'
#' @param ... Named arguments matching \code{\link{download_wid}()} parameters.
#' @return A \code{wid_query} object (a named list with class \code{"wid_query"}).
#' @examples
#' q <- wid_query(indicators = "sptinc992j", areas = "US", years = 2010:2020)
#' @export
wid_query <- function(...) {
  defaults <- lapply(formals(download_wid), function(x) if (is.symbol(x)) NULL else x)
  args     <- list(...)
  bad      <- setdiff(names(args), names(defaults))
  if (length(bad))
    warning("Unknown parameter(s) ignored: ", paste(bad, collapse = ", "), call. = FALSE)
  structure(modifyList(defaults, args[intersect(names(args), names(defaults))]),
            class = "wid_query")
}

#' Update a wid_query object
#'
#' Modify fields of a \code{wid_query} object, returning an updated query.
#'
#' @param query A \code{wid_query} object.
#' @param ...   Fields to update.
#' @return The updated \code{wid_query} object.
#' @examples
#' q <- wid_query(indicators = "sptinc992j", areas = "US")
#' q <- wid_filter(q, years = 2010:2020)
#' @export
wid_filter <- function(query, ...) {
  stopifnot(inherits(query, "wid_query"))
  for (nm in names(list(...))) query[[nm]] <- list(...)[[nm]]
  query
}

#' Execute a wid_query
#'
#' Fetch data from WID.world using a \code{wid_query} object.
#'
#' @param query A \code{wid_query} object.
#' @param ...   Parameter overrides.
#' @return A \code{wid_df} (a classed \code{data.frame}); see \code{\link{download_wid}}.
#' @examples
#' \donttest{
#'   if (nzchar(Sys.getenv("WID_API_KEY"))) {
#'     q <- wid_query(indicators = "sptinc992j", areas = "US", years = 2010:2020)
#'     wid_fetch(q)
#'   }
#' }
#' @export
wid_fetch <- function(query, ...) {
  stopifnot(inherits(query, "wid_query"))
  args <- modifyList(as.list(query), list(...))
  do.call(download_wid, args[intersect(names(args), names(formals(download_wid)))])
}

#' Print a wid_query object
#'
#' Displays the contents of a \code{wid_query} object.
#'
#' @param x A \code{wid_query} object.
#' @param ... Additional arguments (currently ignored).
#' @return Invisibly returns the input \code{x}.
#' @examples
#' q <- wid_query(indicators = "sptinc992j", areas = "US")
#' print(q)
#' @export
print.wid_query <- function(x, ...) {
  cat("<wid_query>\n")
  for (nm in names(x)) {
    v <- x[[nm]]
    s <- if (is.null(v)) "NULL"
    else if (length(v) > 4L) paste0(paste(head(v, 4L), collapse = ", "), " ...[", length(v), "]")
    else paste(v, collapse = ", ")
    cat(" ", nm, ": ", s, "\n", sep = "")
  }
  invisible(x)
}