#' R-squared of Poisson GLM predictions vs. observations
#'
#' @description
#' Fits a Poisson GLM model \code{y ~ x} with the numeric response \code{y} and the numeric predictor \code{x} using \code{stats::glm()} and returns the R-squared of the observations against the predictions (see [score_r2()]).
#'
#' Supports cross-validation via the arguments arguments \code{cv_training_fraction} (numeric between 0 and 1) and \code{cv_iterations} (integer between 1 and \code{n}) introduced via ellipsis (\code{...}). See [preference_order()] for further details.
#'
#' @param df (required, dataframe) with columns:
#' \itemize{
#'   \item "x": (numeric, character, factor) predictor.
#'   \item "y" (integer) counts response.
#' }
#' @inheritParams f_numeric_glm
#' @return numeric or numeric vector: R-squared
#' @examples
#'
#' data(vi_smol, package = "spatialData")
#'
#' df <- data.frame(
#'   y = vi_smol[["vi_counts"]],
#'   x = vi_smol[["swi_max"]]
#' )
#'
#' #no cross-validation
#' f_count_glm(df = df)
#'
#' #cross-validation
#' f_count_glm(
#'   df = df,
#'   cv_training_fraction = 0.5,
#'   cv_iterations = 10
#'   )
#'
#' #categorical predictor
#' df <- data.frame(
#'   y = vi_smol[["vi_counts"]],
#'   x = vi_smol[["koppen_zone"]]
#' )
#'
#' f_count_glm(df = df)
#'
#' @family preference_order_functions
#' @autoglobal
#' @export
f_count_glm <- function(
  df,
  ...
) {
  dots <- list(...)

  cv_training_fraction <- if (is.null(dots$cv_training_fraction)) {
    1
  } else {
    dots$cv_training_fraction
  }

  cv_iterations <- if (is.null(dots$cv_iterations)) 1 else dots$cv_iterations

  function_name <- validate_arg_function_name(
    default_name = "collinear::f_count_glm()",
    function_name = dots$function_name
  )

  df <- stats::na.omit(object = df)

  #check column names in df
  if (!all(c("x", "y") %in% names(df))) {
    stop(
      "\n",
      function_name,
      ": dataframe 'df' must have the column names 'x' and 'y'.",
      call. = FALSE
    )
  }

  if (
    !is.numeric(df[["y"]]) ||
      !is.integer(df[["y"]])
  ) {
    stop(
      "\n",
      function_name,
      ": column 'y' of dataframe 'df' must be numeric integer.",
      call. = FALSE
    )
  }

  scores <- rep(x = NA_real_, times = cv_iterations)

  #iterations
  for (i in seq_len(cv_iterations)) {
    #data split
    if (cv_training_fraction < 1) {
      train_indices <- sample(
        x = nrow(df),
        size = floor(nrow(df) * cv_training_fraction),
        replace = FALSE
      )

      train_df <- df[train_indices, , drop = FALSE]
      test_df <- df[-train_indices, , drop = FALSE]

      if (!is.numeric(df[["x"]])) {
        test_df <- test_df[test_df[["x"]] %in% train_df[["x"]], ]
      }
    } else {
      train_df <- df
      test_df <- df
    }

    if (nrow(test_df) < 2) {
      next
    }

    scores[i] <- tryCatch(
      {
        #train
        m <- stats::glm(
          formula = y ~ x,
          data = train_df,
          family = stats::poisson(
            link = "log"
          )
        )

        #predict
        p <- stats::predict(
          object = m,
          newdata = test_df,
          type = "response"
        )

        #evaluate
        score <- score_r2(
          o = test_df[["y"]],
          p = p,
          function_name = function_name
        )
      },
      error = function(e) {
        return(NA)
      }
    )
  }

  scores
}
