#' StandardOutput R6 Class
#'
#' @description
#' Manages API-specific standard output fields that should be excluded from NDA processing.
#' These are metadata fields automatically generated by data collection platforms.
#'
#' Standard output fields are removed early in the nda() workflow to prevent them from
#' appearing in submission templates or data definition files.
#'
#' @keywords internal
#' @noRd
StandardOutput <- R6::R6Class(
  "StandardOutput",
  
  public = list(
    #' @field redcap Character vector - REDCap standard output fields
    #' - {measure_name}_complete: Form completion status
    #' - record_id: REDCap internal record ID (alias for src_subject_id)
    #' - redcap_event_name: Longitudinal event name
    redcap = c("{measure_name}_complete", "record_id", "redcap_event_name"),
    
    #' @field qualtrics Character vector - Qualtrics standard output fields
    qualtrics = c(
      "StartDate", "EndDate", "Status", "Progress", "Duration (in seconds)",
      "Finished", "RecordedDate", "ResponseId", "DistributionChannel",
      "UserLanguage", "candidateId", "studyId", "measure", "ATTN", "ATTN_1", "SC0",
      "IPAddress", "RecipientLastName", "RecipientFirstName", "RecipientEmail",
      "ExternalReference", "LocationLatitude", "LocationLongitude"
    ),
    
    #' @field mongo Character vector - MongoDB standard output fields
    mongo = c("_id"),
    
    #' @field sql Character vector - SQL standard output fields
    sql = character(0),
    
    #' @field oracle Character vector - Oracle standard output fields
    oracle = character(0),
    
    #' @field csv Character vector - CSV standard output fields
    csv = character(0),
    
    #' @description
    #' Get standard output fields for a specific API
    #'
    #' @param api Character. API type (redcap, qualtrics, mongo, sql, oracle, csv)
    #' @param measure_name Character. Measure name for dynamic substitution (e.g., {measure_name}_complete)
    #' @param categorical_vars CategoricalVariables instance (optional). If provided,
    #'   categorical variables will be excluded from the removal list to prevent accidental deletion
    #'
    #' @return Character vector of field names to remove
    get_fields = function(api, measure_name = NULL, categorical_vars = NULL) {
      if (!api %in% names(self)) {
        warning(sprintf("Unknown API type: %s. Returning empty field list.", api))
        return(character(0))
      }
      
      fields <- self[[api]]
      
      # Replace {measure_name} placeholder if provided
      if (!is.null(measure_name) && length(fields) > 0) {
        fields <- gsub("\\{measure_name\\}", measure_name, fields)
      }
      
      # Preserve categorical variables if provided
      # This prevents accidental removal if a categorical variable has the same name
      # as a standard output field (which would be an error condition)
      if (!is.null(categorical_vars) && inherits(categorical_vars, "CategoricalVariables")) {
        all_categorical <- categorical_vars$get_all()
        
        # Check for overlap (error condition)
        overlap <- intersect(fields, all_categorical)
        if (length(overlap) > 0) {
          stop(sprintf(
            "Field name conflict: The following field(s) appear in both StandardOutput and CategoricalVariables: %s. This is not allowed.",
            paste(overlap, collapse=", ")
          ))
        }
      }
      
      return(fields)
    },
    
    #' @description
    #' Remove standard output fields from a dataframe
    #'
    #' @param df Data frame to clean
    #' @param api Character. API type
    #' @param measure_name Character. Measure name
    #' @param categorical_vars CategoricalVariables instance (optional). Prevents removal of categorical fields
    #' @param verbose Logical. Whether to print verbose messages
    #'
    #' @return Cleaned data frame with standard output fields removed
    remove_from_df = function(df, api, measure_name = NULL, categorical_vars = NULL, verbose = FALSE) {
      if (!is.data.frame(df)) {
        warning("Input is not a data frame. Returning unchanged.")
        return(df)
      }
      
      # Get fields to remove (with categorical variable check)
      fields_to_remove <- self$get_fields(api, measure_name, categorical_vars)
      
      if (length(fields_to_remove) == 0) {
        return(df)
      }
      
      # Find which fields actually exist in df
      existing_to_remove <- intersect(names(df), fields_to_remove)
      
      if (length(existing_to_remove) == 0) {
        if (verbose) {
          message(sprintf("[CLEANUP] No %s standard output fields found in dataframe", toupper(api)))
        }
        return(df)
      }
      
      # Always show removal message (even if verbose=FALSE)
      # This is important for transparency about what's being removed
      if (length(existing_to_remove) > 0) {
        message(sprintf("[CLEANUP] Removing %d %s standard output field(s): %s",
                       length(existing_to_remove),
                       toupper(api),
                       paste(existing_to_remove, collapse=", ")))
      }
      
      # Check if any categorical variables exist in dataframe
      # Show message about preservation (non-verbose mode)
      if (!is.null(categorical_vars)) {
        categorical_in_df <- categorical_vars$get_existing(df)
        if (length(categorical_in_df) > 0) {
          message(sprintf("[CLEANUP] Detected categorical variables: %s - adding to data structure and submission",
                         paste(categorical_in_df, collapse=", ")))
        }
      }
      
      # Remove fields
      df[, !names(df) %in% existing_to_remove, drop = FALSE]
    }
  )
)
