#' Plot Annual Publication Counts
#'
#' @description Draw a stacked bar chart of INLPO publication counts by year,
#'   with USGS and non-USGS publications distinguished by color.
#'
#' @param pubs 'pub' data frame.
#'   Publications of the INLPO; see the [`pubs`] dataset for the expected data
#'   format. Defaults to `inlpubs::pubs`.
#'
#' @param author_id 'character' vector, or `NULL`.
#'   One or more author identifiers used to subset `pubs` to publications that
#'   any of those authors appear on before plotting. The default (`NULL`)
#'   includes all publications (no author filtering).
#'
#' @param full_range 'logical' flag.
#'   Whether the x-axis spans the year range of all publications in `pubs`
#'   (before any author filtering). If `TRUE` (the default), the timeline is
#'   fixed to the full dataset, making per-author charts directly comparable.
#'   If `FALSE`, the timeline is limited to the year range of the filtered
#'   publications. Has no effect when `author_id` is `NULL`.
#'
#' @param legend_loc 'character' string.
#'   Position of the legend, passed as the keyword location to
#'   [graphics::legend()]. One of `"topleft"` (the default), `"topright"`,
#'   `"bottomleft"`, `"bottomright"`, `"top"`, `"bottom"`, `"left"`,
#'   `"right"`, or `"center"`.
#'
#' @param colors 'character' vector of length 2.
#'   Fill colors for the two series, in the order `c("USGS", "Non-USGS")`.
#'   Defaults to steel blue and burnt orange.
#'
#' @param ...
#'   Additional arguments passed to [graphics::barplot()].
#'
#' @return Invisibly returns a numeric vector giving the x-axis coordinates of
#'   the bar midpoints, as returned by [graphics::barplot()].
#'
#' @author J.C. Fisher, U.S. Geological Survey, Idaho Water Science Center
#'
#' @export
#'
#' @seealso [`pubs`] for the publications dataset.
#'
#' @examples
#' plot_pubs()

plot_pubs <- function(
  pubs = inlpubs::pubs,
  author_id = NULL,
  full_range = TRUE,
  legend_loc = "topleft",
  colors = c("USGS" = "#4682B4", "Non-USGS" = "#B47846"),
  ...
) {
  # check arguments
  checkmate::assert_class(pubs, classes = c("pub", "data.frame"))
  checkmate::assert_character(author_id, any.missing = FALSE, min.len = 1, null.ok = TRUE)
  checkmate::assert_flag(full_range)
  checkmate::assert_choice(
    legend_loc,
    choices = c(
      "topleft",
      "topright",
      "bottomleft",
      "bottomright",
      "top",
      "bottom",
      "left",
      "right",
      "center"
    )
  )
  checkmate::assert_character(colors, len = 2, any.missing = FALSE)
  checkmate::assert_names(names(colors), identical.to = c("USGS", "Non-USGS"))

  # capture the full year range before filtering (for a fixed timeline)
  full_yrs <- range(pubs$year)

  # subset to publications any of the requested authors appear on (NULL keeps all)
  if (!is.null(author_id)) {
    is_match <- vapply(
      pubs$author_id,
      FUN = function(ids) any(author_id %in% ids),
      FUN.VALUE = logical(1)
    )
    pubs <- pubs[is_match, , drop = FALSE]
    if (nrow(pubs) == 0L) {
      stop("No publications found for the specified author_id(s).", call. = FALSE)
    }
  }

  # tally counts per year over a gap-free year sequence (empty years are zero)
  yr_range <- if (full_range) full_yrs else range(pubs$year)
  all_yrs <- yr_range[1]:yr_range[2]
  is_usgs <- pubs$institution %in% "U.S. Geological Survey"
  total_by_yr <- table(factor(pubs$year, levels = all_yrs))
  usgs_by_yr <- table(factor(pubs$year[is_usgs], levels = all_yrs))

  # stack USGS and non-USGS series into a 2-row matrix
  counts <- rbind(
    "USGS" = as.integer(usgs_by_yr),
    "Non-USGS" = as.integer(total_by_yr) - as.integer(usgs_by_yr)
  )
  y_breaks <- pretty(c(0, max(colSums(counts))))
  y_breaks <- as.integer(y_breaks[y_breaks %% 1 == 0])
  ylim <- range(y_breaks)

  # draw the stacked bars with custom axes (margins restored on exit)
  old_par <- graphics::par(mar = c(2.1, 3.1, 1.1, 0.1))
  on.exit(graphics::par(old_par), add = TRUE)
  mp <- graphics::barplot(
    height = counts,
    ylab = "Number of publications per year",
    col = colors,
    space = 0.2,
    border = NA,
    ylim = ylim,
    yaxt = "n",
    xaxt = "n",
    cex.lab = 0.7,
    mgp = c(2, 0.5, 0),
    ...
  )

  # label years at pretty breaks (clamped to range, falling back to all years)
  pretty_yrs <- pretty(all_yrs, n = 7)
  pretty_yrs <- pretty_yrs[pretty_yrs >= min(all_yrs) & pretty_yrs <= max(all_yrs)]
  label_yrs <- if (length(pretty_yrs)) pretty_yrs else all_yrs
  is_label <- all_yrs %in% label_yrs

  # axes: labeled bottom/left, mirrored unlabeled top/right
  graphics::axis(
    1,
    at = mp[is_label],
    labels = all_yrs[is_label],
    tck = 0.02,
    lwd = 0,
    lwd.ticks = 0.5,
    cex.axis = 0.7,
    mgp = c(2, 0.5, 0)
  )
  graphics::axis(
    2,
    at = y_breaks,
    las = 2,
    tck = 0.02,
    lwd = 0,
    lwd.ticks = 0.5,
    cex.axis = 0.7,
    mgp = c(2, 0.5, 0)
  )
  graphics::axis(
    3,
    at = mp[is_label],
    labels = NA,
    tck = 0.02,
    lwd = 0,
    lwd.ticks = 0.5
  )
  graphics::axis(
    4,
    at = y_breaks,
    labels = NA,
    las = 2,
    tck = 0.02,
    lwd = 0,
    lwd.ticks = 0.5
  )

  # legend (only series present) and plot frame
  present <- rowSums(counts) > 0
  graphics::legend(
    x = legend_loc,
    legend = names(colors)[present],
    fill = colors[present],
    border = NA,
    inset = c(0.02, 0.05),
    cex = 0.7,
    box.lty = 1,
    box.lwd = 0.5,
    xpd = NA,
    bg = "#FFFFFFE7"
  )
  graphics::box(lwd = 0.5)

  invisible(mp)
}
