#==========================================================
# Group Sequence Check
#==========================================================

#' Check Sequence Integrity Within Groups
#'
#' Checks a numeric or integer sequence independently within
#' groups for missing values, duplicates, ordering problems,
#' gaps, and step mismatches.
#'
#' @param data A data.frame.
#' @param group A single character string specifying the
#'   grouping variable.
#' @param variable A single character string specifying the
#'   numeric or integer sequence variable.
#' @param step A positive finite numeric value specifying the
#'   expected increment. Default is 1.
#'
#' @return A data.frame of class \code{GroupSequenceCheck}
#'   containing row-level sequence diagnostics.
#'
#' @details
#' Sequence integrity is evaluated independently within each
#' group and in the original row order.
#'
#' Missing values in the grouping variable are retained and
#' treated as a separate missing group for diagnostic
#' purposes.
#'
#' Missing sequence values are reported separately and are
#' excluded when calculating previous values and differences.
#'
#' All occurrences of duplicated non-missing sequence values
#' within a group are flagged.
#'
#' A gap occurs when the difference from the previous
#' non-missing value within the same group is greater than
#' \code{step}.
#'
#' @examples
#' dat <- data.frame(
#'   Animal_ID = c(
#'     "A001", "A001", "A001",
#'     "A002", "A002", "A002"
#'   ),
#'   Visit = c(
#'     1, 2, 4,
#'     1, 2, 2
#'   )
#' )
#'
#' group_sequence_check(
#'   dat,
#'   group = "Animal_ID",
#'   variable = "Visit"
#' )
#'
#' @export

group_sequence_check <- function(
    data,
    group,
    variable,
    step = 1
) {

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

  #---------------------------------------------
  # Helper: validate variable name
  #---------------------------------------------
  validate_name <- function(value, argument) {

    if (
      !is.character(value) ||
      length(value) != 1L ||
      is.na(value) ||
      !nzchar(value)
    ) {
      stop(
        sprintf(
          "'%s' must be a single non-empty character string.",
          argument
        ),
        call. = FALSE
      )
    }

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

    invisible(TRUE)
  }

  #---------------------------------------------
  # Required arguments
  #---------------------------------------------
  if (missing(group)) {
    stop(
      "'group' must be supplied.",
      call. = FALSE
    )
  }

  if (missing(variable)) {
    stop(
      "'variable' must be supplied.",
      call. = FALSE
    )
  }

  validate_name(
    group,
    "group"
  )

  validate_name(
    variable,
    "variable"
  )

  #---------------------------------------------
  # Group and sequence variables must differ
  #---------------------------------------------
  if (identical(group, variable)) {
    stop(
      "'group' and 'variable' must refer to different variables.",
      call. = FALSE
    )
  }

  #---------------------------------------------
  # Validate step
  #---------------------------------------------
  if (
    !is.numeric(step) ||
    length(step) != 1L ||
    is.na(step) ||
    !is.finite(step) ||
    step <= 0
  ) {
    stop(
      "'step' must be a single positive finite numeric value.",
      call. = FALSE
    )
  }

  #---------------------------------------------
  # Extract variables
  #---------------------------------------------
  g <- data[[group]]
  x <- data[[variable]]

  #---------------------------------------------
  # Validate sequence variable
  #---------------------------------------------
  if (!is.numeric(x)) {
    stop(
      "'variable' must refer to a numeric or integer variable.",
      call. = FALSE
    )
  }

  #---------------------------------------------
  # Missing indicators
  #---------------------------------------------
  group_missing <- is.na(g)
  missing_value <- is.na(x)

  #---------------------------------------------
  # Initialize output
  #---------------------------------------------
  duplicate <- rep(
    FALSE,
    length(x)
  )

  previous_value <- rep(
    NA_real_,
    length(x)
  )

  difference <- rep(
    NA_real_,
    length(x)
  )

  order_issue <- rep(
    FALSE,
    length(x)
  )

  gap <- rep(
    FALSE,
    length(x)
  )

  step_mismatch <- rep(
    FALSE,
    length(x)
  )

  #---------------------------------------------
  # Construct grouping key
  #---------------------------------------------
  group_key <- as.character(g)

  group_key[group_missing] <-
    "<NA_GROUP>"

  group_indices <- split(
    seq_along(x),
    group_key
  )

  #---------------------------------------------
  # Evaluate each group
  #---------------------------------------------
  for (idx in group_indices) {

    available <-
      idx[!missing_value[idx]]

    #-------------------------------------------
    # Duplicate values within group
    #-------------------------------------------
    if (length(available) > 0L) {

      duplicate[available] <-
        duplicated(x[available]) |
        duplicated(
          x[available],
          fromLast = TRUE
        )
    }

    #-------------------------------------------
    # Sequence differences
    #-------------------------------------------
    if (length(available) >= 2L) {

      current_index <-
        available[-1L]

      previous_index <-
        available[-length(available)]

      previous_value[current_index] <-
        x[previous_index]

      difference[current_index] <-
        x[current_index] -
        x[previous_index]
    }
  }

  #---------------------------------------------
  # Comparable observations
  #---------------------------------------------
  comparable <-
    !is.na(difference)

  #---------------------------------------------
  # Ordering problems
  #---------------------------------------------
  order_issue[comparable] <-
    difference[comparable] <= 0

  #---------------------------------------------
  # Gaps
  #---------------------------------------------
  gap[comparable] <-
    difference[comparable] > step

  #---------------------------------------------
  # Step mismatch
  #---------------------------------------------
  step_mismatch[comparable] <-
    difference[comparable] > 0 &
    difference[comparable] != step

  #---------------------------------------------
  # Overall issue
  #---------------------------------------------
  sequence_issue <-
    group_missing |
    missing_value |
    duplicate |
    order_issue |
    step_mismatch

  valid_sequence <-
    !sequence_issue

  #---------------------------------------------
  # Construct result
  #---------------------------------------------
  result <- data.frame(
    Row = seq_along(x),
    Group = g,
    Value = x,
    GroupMissing = group_missing,
    Missing = missing_value,
    Duplicate = duplicate,
    PreviousValue = previous_value,
    Difference = difference,
    OrderIssue = order_issue,
    Gap = gap,
    StepMismatch = step_mismatch,
    SequenceIssue = sequence_issue,
    ValidSequence = valid_sequence,
    stringsAsFactors = FALSE,
    check.names = FALSE
  )

  #---------------------------------------------
  # Assign class
  #---------------------------------------------
  class(result) <- c(
    "GroupSequenceCheck",
    "data.frame"
  )

  result
}
