#==========================================================
# Length Check
#==========================================================

#' Check Character Length
#'
#' Checks the number of characters in a character or factor
#' variable and identifies values outside user-defined
#' length limits.
#'
#' @param data A data.frame.
#' @param variable A single character string specifying the
#'   variable to check.
#' @param min_length Optional minimum acceptable number of
#'   characters.
#' @param max_length Optional maximum acceptable number of
#'   characters.
#' @param exact_length Optional exact required number of
#'   characters.
#'
#' @return A data.frame of class \code{LengthCheck}
#'   containing row-level length diagnostics.
#'
#' @details
#' The selected variable must be character or factor.
#'
#' Missing values are reported separately and are not
#' classified as length violations.
#'
#' If \code{exact_length} is supplied, \code{min_length}
#' and \code{max_length} must both be NULL.
#'
#' @examples
#' dat <- data.frame(
#'   Sample_ID = c("A01", "A002", "B12", NA)
#' )
#'
#' length_check(
#'   dat,
#'   variable = "Sample_ID",
#'   exact_length = 3
#' )
#'
#' @export

length_check <- function(
    data,
    variable,
    min_length = NULL,
    max_length = NULL,
    exact_length = NULL
) {

  #---------------------------------------------
  # Validate data
  #---------------------------------------------
  validate_data(data)

  #---------------------------------------------
  # Validate variable
  #---------------------------------------------
  if (
    missing(variable) ||
    !is.character(variable) ||
    length(variable) != 1L ||
    is.na(variable) ||
    !nzchar(variable)
  ) {
    stop(
      "'variable' must be a single non-empty character string.",
      call. = FALSE
    )
  }

  #---------------------------------------------
  # Check variable exists
  #---------------------------------------------
  if (!variable %in% names(data)) {
    stop(
      sprintf(
        "Variable '%s' was not found in 'data'.",
        variable
      ),
      call. = FALSE
    )
  }

  #---------------------------------------------
  # Check variable type
  #---------------------------------------------
  x <- data[[variable]]

  if (!is.character(x) && !is.factor(x)) {
    stop(
      "'variable' must refer to a character or factor variable.",
      call. = FALSE
    )
  }

  #---------------------------------------------
  # Validate length arguments
  #---------------------------------------------
  validate_length <- function(value, argument) {

    if (is.null(value)) {
      return(NULL)
    }

    if (
      !is.numeric(value) ||
      length(value) != 1L ||
      is.na(value) ||
      !is.finite(value) ||
      value < 0 ||
      value != floor(value)
    ) {
      stop(
        sprintf(
          "'%s' must be a single non-negative integer.",
          argument
        ),
        call. = FALSE
      )
    }

    as.integer(value)
  }

  min_length <- validate_length(
    min_length,
    "min_length"
  )

  max_length <- validate_length(
    max_length,
    "max_length"
  )

  exact_length <- validate_length(
    exact_length,
    "exact_length"
  )

  #---------------------------------------------
  # Validate combinations
  #---------------------------------------------
  if (
    !is.null(exact_length) &&
    (!is.null(min_length) || !is.null(max_length))
  ) {
    stop(
      "'exact_length' cannot be combined with 'min_length' or 'max_length'.",
      call. = FALSE
    )
  }

  if (
    !is.null(min_length) &&
    !is.null(max_length) &&
    min_length > max_length
  ) {
    stop(
      "'min_length' cannot be greater than 'max_length'.",
      call. = FALSE
    )
  }

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

  #---------------------------------------------
  # Missing values and string lengths
  #---------------------------------------------
  missing_value <- is.na(value)

  string_length <- nchar(
    value,
    type = "chars",
    allowNA = TRUE
  )

  #---------------------------------------------
  # Below minimum
  #---------------------------------------------
  below_min <- rep(
    FALSE,
    length(value)
  )

  if (!is.null(min_length)) {
    below_min <-
      !missing_value &
      string_length < min_length
  }

  #---------------------------------------------
  # Above maximum
  #---------------------------------------------
  above_max <- rep(
    FALSE,
    length(value)
  )

  if (!is.null(max_length)) {
    above_max <-
      !missing_value &
      string_length > max_length
  }

  #---------------------------------------------
  # Exact-length mismatch
  #---------------------------------------------
  exact_mismatch <- rep(
    FALSE,
    length(value)
  )

  if (!is.null(exact_length)) {
    exact_mismatch <-
      !missing_value &
      string_length != exact_length
  }

  #---------------------------------------------
  # Overall validity
  #---------------------------------------------
  valid_length <-
    !missing_value &
    !below_min &
    !above_max &
    !exact_mismatch

  #---------------------------------------------
  # Result
  #---------------------------------------------
  result <- data.frame(
    Row = seq_along(x),
    Value = value,
    Length = string_length,
    Missing = missing_value,
    BelowMin = below_min,
    AboveMax = above_max,
    ExactMismatch = exact_mismatch,
    ValidLength = valid_length,
    stringsAsFactors = FALSE,
    check.names = FALSE
  )

  class(result) <- c(
    "LengthCheck",
    "data.frame"
  )

  result
}
