#==========================================================
# Blank Value Check
#==========================================================

#' Check Blank Values
#'
#' Identifies blank values in character and factor variables.
#'
#' @param data A data.frame.
#' @param trim Logical. If TRUE, strings containing only
#'   whitespace are treated as blank. Default is TRUE.
#'
#' @return A data.frame of class \code{BlankCheck}
#'   containing variable-level blank-value diagnostics.
#'
#' @details
#' Blank values are evaluated only for character and factor
#' variables. Missing values (\code{NA}) are reported
#' separately and are not counted as blank values.
#'
#' When \code{trim = TRUE}, leading and trailing whitespace
#' is removed before checking whether a value is blank.
#' Therefore values such as \code{" "}, \code{"   "}, and
#' \code{"\\t"} are classified as blank.
#'
#' @examples
#' dat <- data.frame(
#'   name = c("Dog", "", "Cat", "   ", NA),
#'   value = 1:5,
#'   stringsAsFactors = FALSE
#' )
#'
#' blank_check(dat)
#'
#' blank_check(dat, trim = FALSE)
#'
#' @export

blank_check <- function(data, trim = TRUE) {

  #---------------------------------------------
  # Validation
  #---------------------------------------------
  validate_data(data)

  if (!is.logical(trim) ||
      length(trim) != 1L ||
      is.na(trim)) {

    stop(
      "'trim' must be TRUE or FALSE.",
      call. = FALSE
    )
  }

  #---------------------------------------------
  # Analyze one variable
  #---------------------------------------------
  analyse_variable <- function(x) {

    text_variable <-
      is.character(x) ||
      is.factor(x)

    missing <- sum(is.na(x))

    if (!text_variable) {

      return(
        list(
          TextVariable = FALSE,
          Missing = missing,
          Blank = 0L
        )
      )
    }

    #-------------------------------------------
    # Convert factor safely to character
    #-------------------------------------------
    values <- as.character(x)

    #-------------------------------------------
    # Exclude missing values
    #-------------------------------------------
    observed <- values[!is.na(values)]

    #-------------------------------------------
    # Optional whitespace trimming
    #-------------------------------------------
    if (trim) {

      observed <- trimws(observed)

    }

    #-------------------------------------------
    # Count blanks
    #-------------------------------------------
    blank <- sum(
      observed == ""
    )

    list(
      TextVariable = TRUE,
      Missing = missing,
      Blank = as.integer(blank)
    )
  }

  #---------------------------------------------
  # Apply to variables
  #---------------------------------------------
  diagnostics <- lapply(
    data,
    analyse_variable
  )

  #---------------------------------------------
  # Number of rows
  #---------------------------------------------
  n <- nrow(data)

  #---------------------------------------------
  # Construct result
  #---------------------------------------------
  result <- data.frame(

    Variable = names(data),

    TextVariable = vapply(
      diagnostics,
      function(x) x$TextVariable,
      logical(1)
    ),

    Missing = vapply(
      diagnostics,
      function(x) x$Missing,
      integer(1)
    ),

    Blank = vapply(
      diagnostics,
      function(x) x$Blank,
      integer(1)
    ),

    stringsAsFactors = FALSE,
    check.names = FALSE
  )

  #---------------------------------------------
  # Blank percentage
  #---------------------------------------------
  if (n == 0L) {

    result$BlankPercent <- rep(
      NA_real_,
      ncol(data)
    )

  } else {

    result$BlankPercent <- round(
      100 * result$Blank / n,
      2
    )

  }

  #---------------------------------------------
  # Flag variables
  #---------------------------------------------
  result$HasBlank <- result$Blank > 0L

  #---------------------------------------------
  # Class
  #---------------------------------------------
  class(result) <- c(
    "BlankCheck",
    "data.frame"
  )

  result
}
