#' Obtain Image from a PDF Document
#'
#' @description Render a single page of a PDF document to a JPEG image. Typically
#'   used to capture a publication's cover (the first page) as a thumbnail.
#'   Requires that the \pkg{pdftools}, \pkg{magick}, and \pkg{callr} packages are
#'   available.
#'
#' @details The selected page is rendered at a resolution computed from the
#'   requested output width (to avoid rasterizing a needlessly large image),
#'   resized with a high-quality Lanczos filter while preserving aspect ratio,
#'   flattened onto a white background, converted to an sRGB JPEG at the given
#'   color depth, and stripped of metadata before being written.
#'
#'   The Poppler-backed reads and the page rasterization are run in a child
#'   process, so a native crash on a problematic PDF (which cannot be caught with
#'   `tryCatch`) surfaces here as a normal R error instead of taking down the
#'   session or aborting a batch run.
#'
#' @param input 'character' string.
#'   File path to the PDF document.
#'
#' @param output 'character' string.
#'   File path for the JPEG image to write. If the path lacks a `.jpg` or `.jpeg`
#'   extension, `.jpg` is appended. Defaults to a temporary file.
#'
#' @param page 'integer' number.
#'   Page number to render. Defaults to 1 (the cover page).
#'
#' @param width 'integer' number.
#'   Output image width in pixels. Height is set automatically to preserve the
#'   page's aspect ratio. Defaults to 300.
#'
#' @param depth 'integer' number.
#'   Image color depth, either 8 or 16. Defaults to 8.
#'
#' @param timeout 'numeric' number.
#'   Maximum time, in seconds, to allow the PDF page to render before the operation is aborted.
#'
#' @param quality 'integer' number between 0 and 100.
#'   JPEG compression quality. Defaults to 70.
#'
#' @return Returns the path to the image file, invisibly.
#'
#' @author J.C. Fisher, U.S. Geological Survey, Idaho Water Science Center
#'
#' @export
#'
#' @seealso [`add_content`] to add cover images to the \pkg{inlpubs} package.
#'
#' @examples
#' input <- system.file("extdata", "test.pdf", package = "inlpubs")
#' path <- get_pdf_image(input)
#'
#' unlink(path)

get_pdf_image <- function(
  input,
  output = tempfile(fileext = ".jpg"),
  page = 1,
  width = 300,
  depth = 8,
  quality = 70,
  timeout = 120
) {
  # check system dependencies
  if (!requireNamespace("pdftools", quietly = TRUE)) {
    stop("Reading a PDF requires the 'pdftools' package", call. = FALSE)
  }
  if (!requireNamespace("magick", quietly = TRUE)) {
    stop("Image processing requires the 'magick' package", call. = FALSE)
  }
  if (!requireNamespace("callr", quietly = TRUE)) {
    stop("Crash-safe PDF reading requires the 'callr' package", call. = FALSE)
  }

  # check arguments
  input <- path.expand(input) |> normalizePath(winslash = "/", mustWork = FALSE)
  checkmate::assert_file_exists(input, access = "r", extension = "pdf")
  output <- path.expand(output) |> normalizePath(winslash = "/", mustWork = FALSE)
  checkmate::assert_path_for_output(output, overwrite = TRUE)
  checkmate::assert_int(page, lower = 1)
  checkmate::assert_int(width, lower = 1)
  checkmate::assert_subset(depth, choices = c(8, 16))
  checkmate::assert_int(quality, lower = 0, upper = 100)
  checkmate::assert_number(timeout, lower = 1)

  # ensure the output has a JPEG file extension
  if (!grepl("\\.(jpg|jpeg)$", tolower(output))) {
    output <- paste0(output, ".jpg")
  }

  # Run all Poppler work in a child process: read the page count and the page's
  # width, then rasterize the requested page to a PNG file. A magick image object
  # cannot cross the process boundary (it holds an external pointer), so the child
  # writes a PNG and returns its path; the parent does the safe post-processing.
  # A native crash on a problematic PDF is caught here rather than killing R.
  render_dir <- tempfile("pdf_image_")
  dir.create(render_dir, showWarnings = FALSE)
  on.exit(unlink(render_dir, recursive = TRUE), add = TRUE)

  rendered <- tryCatch(
    callr::r(
      func = function(pdf, page, width, dir) {
        # validate the page exists within the child (uses Poppler metadata)
        n_pages <- pdftools::pdf_info(pdf)$pages
        if (page < 1 || page > n_pages) {
          stop(sprintf("page %d out of range (document has %d)", page, n_pages))
        }

        # compute a render resolution sized to the target width rather than
        # rasterizing the page at full resolution and discarding most of it.
        # Page width is in points; inches = points / 72; dpi = target px / inches.
        # A 1.5x headroom keeps the later resize a mild downscale. Falls back to
        # 150 dpi if the page width is missing or invalid, and is clamped.
        page_width_pt <- pdftools::pdf_pagesize(pdf)$width[page]
        if (is.na(page_width_pt) || page_width_pt <= 0) {
          render_dpi <- 150L
        } else {
          page_width_in <- page_width_pt / 72
          render_dpi <- ceiling((width * 1.5) / page_width_in)
          render_dpi <- as.integer(max(72L, min(render_dpi, 300L)))
        }

        # rasterize just the requested page to a PNG (Poppler via magick)
        png_path <- file.path(dir, "page.png")
        magick::image_read_pdf(pdf, pages = page, density = render_dpi) |>
          magick::image_write(path = png_path, format = "png")

        list(png = normalizePath(png_path, winslash = "/", mustWork = FALSE))
      },
      args = list(pdf = input, page = page, width = width, dir = render_dir),
      timeout = timeout
    ),
    error = function(e) NULL
  )
  if (is.null(rendered)) {
    stop(
      "Could not render PDF page (the renderer crashed or timed out on this file): ",
      sQuote(basename(input)),
      call. = FALSE
    )
  }

  # post-process the rendered PNG in the parent (no Poppler involved here):
  #   resize to width with a high-quality filter (aspect ratio preserved),
  #   flatten onto white to avoid transparency rendering as black in JPEG,
  #   convert to sRGB at the requested depth, then strip metadata
  image <- magick::image_read(rendered$png) |>
    magick::image_resize(
      geometry = magick::geometry_size_pixels(width = width),
      filter = "Lanczos"
    ) |>
    magick::image_background(color = "white") |>
    magick::image_flatten() |>
    magick::image_convert(
      colorspace = "sRGB",
      depth = depth
    ) |>
    magick::image_strip()

  # write image as JPEG at the requested quality
  magick::image_write(image, path = output, format = "jpeg", quality = quality)

  # confirm the file was written
  checkmate::assert_file_exists(output, access = "r")

  invisible(output)
}
