#' Fast And Powerful Yet Simple To Use Summarise
#'
#' @description
#' [summarise_plus()] creates a new aggregated data table with the desired grouping.
#' It can output only the deepest nested combination of the grouping variables (default)
#' or you can also output every possible combination of the grouping variables at once,
#' with just one small change. Besides the normal summary functions like sum, mean
#' or median, you can also calculate their respective weighted version by just
#' setting a weight variable.
#'
#' @param data_frame A data frame to summarise.
#' @param class A vector containing all grouping variables.
#' @param values A vector containing all variables that should be summarised.
#' @param statistics Available functions:
#' - "sum"       -> Weighted and unweighted sum
#' - "sum_wgt"   -> Sum of all weights
#' - "freq"      -> Unweighted frequency
#' - "freq_g0"   -> Unweighted frequency of all values greater than zero
#' - "pct_group" -> Weighted and unweighted percentages within the respective group
#' - "pct_total" -> Weighted and unweighted percentages compared to the grand total
#' - "mean"      -> Weighted and unweighted mean
#' - "median"    -> Weighted and unweighted median
#' - "mode"      -> Weighted and unweighted mode
#' - "min"       -> Minimum
#' - "max"       -> Maximum
#' - "sd"        -> Weighted and unweighted standard deviation
#' - "variance"  -> Weighted and unweighted standard variance
#' - "first"     -> First value
#' - "last"      -> Last value
#' - "pn"        -> Weighted and unweighted percentiles (any p1, p2, p3, ... possible)
#' Uses the SAS default way of calculating percentiles, which is Type 2 described in [quantile()].
#' - "missing"   -> Missings generated by the value variables
#' @param formats A list in which is specified which formats should be applied to which
#' class variables.
#' @param types A character vector specifying the different combinations of group
#' variables which should be computed when using nesting = "all". If left empty all
#' possible combinations will be computed.
#' @param weight Put in a weight variable to compute weighted results.
#' @param nesting The predefined value is "deepest" meaning that only the fully
#' nested version of all class variables will be computed. If set to "all", all
#' possible combinations will be computed in one data table. The option "single"
#' only outputs the ungrouped summary of all class variables in one data table.
#' @param merge_back Newly summarised variables can be merged back to the original
#' data frame if TRUE. Only works if nested = "deepest" and no formats are defined.
#' @param na.rm FALSE by default. If TRUE removes all NA values from the class variables.
#' @param print_miss FALSE by default. If TRUE outputs all possible categories of the
#' grouping variables based on the provided formats, even if there are no observations
#' for a combination.
#' @param monitor FALSE by default. If TRUE, outputs two charts to visualize the
#' functions time consumption.
#' @param notes TRUE by default. Prints notifications about NA values produced by
#' class variables during summarise.
#'
#' @details
#' [summarise_plus()] is based on the 'SAS' procedure Proc Summary, which provides
#' efficient and readable ways to perform complex aggregations.
#'
#' Normally you would compute new categorical variables beforehand - probably even in
#' different forms, if you wanted to have different categorizations - and bloat up
#' the data set. After all this recoding footwork you could finally use multiple
#' summaries to compute all the stats you need to then put them back together. With this
#' function this is no more necessary.
#'
#' In [summarise_plus()] you put in the original data frame and let the recoding happen
#' via format containers. This is very efficient, since new variables and categories
#' are only created just before the summarise happens.
#'
#' Additionally you can specify whether you only want to produce the all nested version
#' of all group variables or whether you want to produce every possible combination in
#' one go. All with a single option.
#'
#' The function is optimized to always take the fastest route, depending on the options
#' specified.
#'
#' @return
#' Returns a summarised `data.table`.
#'
#' If `nesting` is set to `"all"` or `"single"`, the returned table automatically includes three hierarchical metadata variables:
#' - **`TYPE`** (character): The active grouping combination for the row (e.g., `"year+sex"`), or `"total"` for the grand total.
#' - **`TYPE_NR`** (integer): A unique sequential identifier assigned to each distinct grouping combination (`TYPE`).
#' - **`DEPTH`** (integer): The number of active grouping variables in the row's combination (e.g., `0` for `"total"`, `1` for single variables, etc.).
#'
#' @seealso
#' Creating formats: [discrete_format()] and [interval_format()].
#'
#' Functions that also make use of formats: [frequencies()], [crosstabs()],
#' [any_table()], [recode.()], [recode_multi()], [transpose_plus()], [sort_plus()].
#'
#' @examples
#' # NOTE: The used threads are set here to prevent a NOTE from the CRAN checks,
#' #       otherwise this is not necessary.
#' old_threads <- data.table::getDTthreads()
#' data.table::setDTthreads(1)
#'
#' # Example formats
#' age. <- discrete_format(
#'     "Total"          = 0:100,
#'     "under 18"       = 0:17,
#'     "18 to under 25" = 18:24,
#'     "25 to under 55" = 25:54,
#'     "55 to under 65" = 55:64,
#'     "65 and older"   = 65:100)
#'
#' sex. <- discrete_format(
#'     "Total"  = 1:2,
#'     "Male"   = 1,
#'     "Female" = 2)
#'
#' income. <- interval_format(
#'     "Total"              =    0:100000,
#'     "below 500"          =    0:500,
#'     "500 to under 1000"  =  500:1000,
#'     "1000 to under 2000" = 1000:2000,
#'     "2000 and more"      = 2000:100000)
#'
#' # Example data frame
#' my_data <- dummy_data(1000)
#'
#' # Summarise the data  with all class variables nested
#' all_nested <- my_data |>
#'     summarise_plus(class      = c(year, sex, age),
#'                    values     = income,
#'                    statistics = c("sum", "pct_group", "pct_total", "sum_wgt", "freq"),
#'                    formats    = list(sex = sex., age = age.),
#'                    weight     = weight,
#'                    nesting    = "deepest",
#'                    na.rm      = TRUE)
#'
#' # Output all possible combinations of the provided class variables. Meaning:
#' # Total, combinations of 2, combinations of 3, ...
#' all_possible <- my_data |>
#'     summarise_plus(class      = c(year, sex, age, income),
#'                    values     = c(probability),
#'                    statistics = c("sum", "p1", "p99", "min", "max", "freq", "freq_g0"),
#'                    formats    = list(sex    = sex.,
#'                                      age    = age.,
#'                                      income = income.),
#'                    weight     = weight,
#'                    nesting    = "all",
#'                    na.rm      = TRUE)
#'
#' # Formats can also be passed as characters
#' single <- my_data |>
#'     summarise_plus(class      = c(year, age, sex),
#'                    values     = weight,
#'                    statistics = c("sum", "mean"),
#'                    formats    = list(sex = "sex.", age = "age."),
#'                    nesting    = "single")
#'
#' # Summarise and merge results back to the original data frame
#' merge_back <- my_data |>
#'     summarise_plus(class      = c(year, age, sex),
#'                    values     = weight,
#'                    statistics = c("sum", "mean"),
#'                    nesting    = "deepest",
#'                    merge_back = TRUE)
#'
#' # Select only certain variable combinations of all the possible combinations
#' certain_types <- my_data |>
#'     summarise_plus(class      = c(year, sex, age),
#'                    values     = c(probability),
#'                    statistics = c("sum", "mean", "freq"),
#'                    formats    = list(sex = sex.,
#'                                      age = age.),
#'                    types      = c("year", "year + age", "age + sex"),
#'                    weight     = weight,
#'                    nesting    = "all",
#'                    na.rm      = TRUE)
#'
#' # You can also select repeating combinations faster like this
#' combi_types <- my_data |>
#'     summarise_plus(class      = c(year, sex, age, education),
#'                    values     = weight,
#'                    statistics = "sum",
#'                    formats    = list(sex = sex.,
#'                                      age = age.),
#'                    types      = "year + sex + (age, age + education, education)",
#'                    nesting    = "all",
#'                    na.rm      = TRUE)
#'
#' # Select specific statistics for specific variables
#' specific_stats <- my_data |>
#'     summarise_plus(class      = c(year, sex),
#'                    statistics = list("sum"       = c(weight, income),
#'                                      "mean"      = expenses,
#'                                      "pct_group" = balance),
#'                    formats    = list(sex = sex.),
#'                    na.rm      = TRUE)
#'
#' # Works without grouping
#' no_group <- my_data |> summarise_plus(values = income)
#'
#' # And also without values
#' no_values  <- my_data |> summarise_plus(class = c(year, sex, age))
#' no_nothing <- my_data |> summarise_plus()
#'
#' # NOTE: The used threads are set here to prevent a NOTE from the CRAN checks,
#' #       otherwise this is not necessary.
#' data.table::setDTthreads(old_threads)
#'
#' @export
summarise_plus <- function(data_frame,
                           class      = NULL,
                           values,
                           statistics = c("sum", "freq"),
                           formats    = list(),
                           types      = "",
                           weight     = NULL,
                           nesting    = "deepest",
                           merge_back = FALSE,
                           na.rm      = .qol_options[["na.rm"]],
                           print_miss = .qol_options[["print_miss"]],
                           monitor    = .qol_options[["monitor"]],
                           notes      = TRUE){
    # Measure the time
    print_start_message()
    print_step("GREY", "Error handling")

    #-------------------------------------------------------------------------#
    monitor_df <- NULL |> monitor_start("Error handling", "Preparation")
    #-------------------------------------------------------------------------#

    ###########################################################################
    # Early evaluations
    ###########################################################################

    # First convert data frame to data table
    if (!data.table::is.data.table(data_frame)){
        data_frame <- data.table::as.data.table(data_frame)
    }

    # Evaluate formats early
    if (!is_list_of_dfs(formats)){
        formats_list <- as.list(substitute(formats))[-1]
        formats      <- evaluate_formats(formats_list)
    }

    # Remove empty formats and throw a warning. This can happen if there is e.g.
    # a typo in the format.
    for (variable in names(formats)){
        if (is.null(formats[[variable]])){
            formats[[variable]] <- NULL
            print_message("WARNING", "Format for variable '[variable]' does not exist and can't be applied.", variable = variable)
        }
    }

    # Look up variable names in format data frame to check whether there is an
    # interval or discrete format
    flag_interval <- FALSE

    for (current_var in names(formats)){
        format_df          <- formats[[current_var]]
        interval_variables <- c("from", "to")
        actual_variables   <- names(format_df)[1:2]

        if (identical(interval_variables, actual_variables)){
            flag_interval <- TRUE
            break
        }
    }

    ###########################################################################
    # Error handling
    ###########################################################################

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Nesting
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    # Correct nesting option if not set right
    if (!nesting %in% c("deepest", "all", "single")){
        print_message("WARNING", c("<Nested> option '[nesting]' doesn't exist. Options 'deepest', 'all' and 'single'",
								   "are available. <Nested> will be set to 'deepest'."), nesting = nesting)
        nesting <- "deepest"
    }

    # Setup of the monitoring results should be displayed depending on the nesting
    monitor_grouping<- "group"

    if (nesting == "deepest"){
        monitor_grouping <- "section"
    }

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Weight
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    weight     <- get_origin_as_char(weight, substitute(weight))
    data_frame <- data_frame |> check_weight(weight)
    weight_var <- ".temp_weight"

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Merge back
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    # Setup necessary options per code if merge_back so that there are no errors
    # even if the user didn't set them up correctly
    if (merge_back){
        if (nesting != "deepest" || length(formats) > 0){
            print_message("WARNING", c("Merging variables back only works with nesting = 'deepest' and only without formats.",
									   "Options will be set accordingly."))
        }

        nesting <- "deepest"
        formats <- list()
    }

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Statistics
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    # Translate statistics if possible
    temp_statistics <- tryCatch({
        # Force evaluation to see if it exists
        get_origin_as_char(statistics, substitute(statistics))
    }, error = function(e){
        # Evaluation failed
        NULL
    })

    # In case statistics couldn't be evaluated, this means a list was passed,
    # which provides specific variables for specific statistics. This list needs
    # to be evaluated differently.
    vars_per_stat_list <- NULL

    if (is.null(temp_statistics)){
        vars_per_stat_list <- lapply(as.list(substitute(statistics)), function(element){
            element <- tolower(as.character(element))

            element[!element %in% "c"]
        })[-1]

        statistics <- names(vars_per_stat_list)
    }
    # Prepare statistics vector as normal
    else{
        # Capture the original statistics in case it is a list of characters
        # passed down by another function for reevaluation below.
        original_statistics <- statistics
        statistics          <- tolower(temp_statistics)
    }

    list_of_statistics <- get_complete_statistics_list(statistics)

    # Get the intersection of the requested statistics to make sure
    # only valid actions are passed down
    requested   <- collapse::funique(unlist(list(statistics)))
    valid_stats <- requested[requested %in% names(list_of_statistics)]

    # In case of another function passing a list of characters, this list is evaluated
    # to the passed symbol instead of the actual list. The statistic validation will
    # therefore result in an empty vector. This case is captured here to reevaluate
    # the statistics based on the original passed list of characters.
    if (length(valid_stats) == 0 && is.list(original_statistics)){
        vars_per_stat_list <- original_statistics
        statistics         <- names(vars_per_stat_list)

        requested   <- collapse::funique(unlist(list(statistics)))
        valid_stats <- requested[requested %in% names(list_of_statistics)]
    }

    # If percentages are selected, make sure sum and sum_wgt are also part of statistics
    flag_remove_sum     <- FALSE
    flag_remove_sum_wgt <- FALSE

    if (any(c("pct_group", "pct_total") %in% tolower(requested))){
        if (!"sum" %in% valid_stats){
            valid_stats     <- c(valid_stats, "sum")
            flag_remove_sum <- TRUE
        }

        if (!"sum_wgt" %in% valid_stats){
            valid_stats         <- c(valid_stats, "sum_wgt")
            flag_remove_sum_wgt <- TRUE
        }
    }

    selected_stats <- list_of_statistics[valid_stats]

    # Check if there are any statistics selected which aren't any kind of sums.
    # If only sums are selected then it is faster to summarise first without formats
    # and then apply the formats to a much smaller data frame.
    only_sums <- valid_stats[!valid_stats %in% c("sum", "sum_wgt", "freq", "freq_g0",
                                                 "missing", "pct_group", "pct_total")]

    # Check for invalid statistics
    invalid_stats <- requested[!requested %in% c(names(list_of_statistics), "sum_wgt",
                                                 "pct_group", "pct_total", paste0("p", 1:100))]

    if (length(invalid_stats) > 0){
        print_message("WARNING", "<Statistic> '[invalid]' is invalid and will be omitted.", invalid = invalid_stats)

        if (length(selected_stats) == 0){
            print_message("WARNING", "No valid <statistic> selected. 'sum' will be used.")

            statistics     <- "sum"
            requested      <- "sum"
            valid_stats    <- "sum"
            selected_stats <- list_of_statistics[valid_stats]
        }
    }

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Class
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    class <- get_origin_as_char(class, substitute(class))

    # Make sure that the variables provided are part of the data frame.
    class <- data_frame |> part_of_df(class)

    # If no grouping variables are provided create a pseudo grouping variable
    if (length(class) == 0){
        class <- "pseudo_class"
        data_frame[["pseudo_class"]] <- 1
    }

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Values
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    values <- get_origin_as_char(values, substitute(values))

    # If no value variables are provided generate them automatically
    if (length(values) <= 1){
        if (length(values) == 0 || values == ""){
            # If a statistics list with variable names is given, extract them an set
            # them as value variables.
            if (!is.null(vars_per_stat_list)){
                values       <- collapse::funique(unlist(vars_per_stat_list))
                names(values) <- NULL
            }
            # Otherwise create a pseudo grouping variable
            else{
                values               <- ".temp_values"
                data_frame[[values]] <- 1
            }
        }
    }

    # Make sure there is no class variable that is also a value variable.
    values <- resolve_intersection(values, class)

    # Make sure that the variables provided are part of the data frame.
    values <- data_frame |> part_of_df(values)

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Types
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    # Enable the use of macro variables
    types <- apply_macro(types)

    # Expand brackets first to be able to identify the unique variables used afterwards
    types_expand <- expand_brackets(types)

    if (types_expand[[2]]){
        types <- names(types_expand[[1]])
    }
    else{
        types_expand <- NULL
    }

    # Get row variables from provided combinations
    type_vars <- unlist_variables(types)

    # If there are type provided
    if (is.null(type_vars)){
        print_message("WARNING", "<Types> must be provided in quotation marks. Types will be ignored.")
        types <- NULL
    }
    else if (length(type_vars) > 0){
        # Exclude total because it will most likely not be part of the data frame
        type_vars <- type_vars[type_vars != "total"]
        type_vars <- data_frame |> part_of_df(type_vars, check_only = TRUE)

        if (is.list(type_vars)){
            print_message("WARNING", c("The provided <type> '[type_vars]' [?is/are] not part of",
									   "the data frame. Types will be ignored."), type_vars = type_vars[[1]])
            types <- NULL
        }

        invalid_types <- type_vars[!type_vars %in% class]

        if (length(invalid_types) > 0){
            print_message("WARNING", c("The provided <type> '[invalid]' [?is/are] not part of",
									   "the <class> variables. Types will be ignored."), invalid = invalid_types)
            types <- NULL
        }

        rm(invalid_types)
    }
    else{
        types <- NULL
    }

    # Get the maximum combination depth to be able to break a loop later on earlier
    max_combination_depth <- 9999

    if (!is.null(types)){
        max_combination_depth <- max(nchar(gsub("[^+]", "", types))) + 1
    }

    rm(type_vars)

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Double entries
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    class  <- remove_doubled_values(class)
    values <- remove_doubled_values(values)

    ###########################################################################
    # Summarisation starts
    ###########################################################################

    #-------------------------------------------------------------------------#
    monitor_df <- monitor_df |> monitor_next("Pre compute", "Preparation")
    #-------------------------------------------------------------------------#

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Some preparations beforehand
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    # If types are specified reorder them alphabetically
    if (length(types) > 0){
        reordered_types <- reorder_combination(types)
    }

    # Get the group vars first
    group_vars <- class

    # Keep hold of original data frame to calculate correct missings for messages and
    # for merge back option
    original_df <- data_frame

    result_df <- NULL

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # If only statistics are provided, which rely on summarisation, the function
    # can take a shortcut by pre summarising the data without any formats.
    # Any additional summarise has way less cases to work with and therefore runs
    # way faster.
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    # Determine whether shortcut is possible
    flag_shortcut <- FALSE

    # Determine when a faster route can be taken to compute the results. Basically
    # this is possible with operations based on sums.
    if (length(only_sums) == 0 && !flag_interval){
        flag_shortcut <- TRUE
    }

    # Pre summarise data frame if only sums as statistics selected
    if (flag_shortcut){
        print_step("MAJOR", "Pre summarise")

        monitor_df <- monitor_df |> monitor_end()

        # Summarise first
        result_list <- data_frame |>
            matrix_summarise(values,
                             group_vars,
                             data_frame[[weight_var]],
                             valid_stats,
                             list_of_statistics,
                             monitor_df,
                             !flag_shortcut)

        data_frame  <- result_list[[1]]
        monitor_df  <- result_list[[2]]
        rm(result_list)

        # Catch new variable names
        values <- data_frame |> inverse(group_vars)
    }

    #-------------------------------------------------------------------------#
    monitor_df <- monitor_df |> monitor_next("Apply formats", "Apply formats")
    #-------------------------------------------------------------------------#

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Nesting crossroad
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    # If only the combination of every grouping variable should be evaluated
    if (tolower(nesting) == "deepest"){
        # Remove NAs from grouping variables
        if (na.rm){
            data_frame <- collapse::na_omit(data_frame, cols = class)
        }

        print_step("MAJOR", "Executing nested merge")
        get_group_missings(original_df[group_vars], notes, na.rm)

        if (!merge_back){
            rm(original_df)
        }

        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        # There are different routes for the shortcut and no shortcut summarise
        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        if (flag_shortcut){
            result_df <- data_frame |> apply_format(formats, group_vars)

            # Final summarise with formatted data frame
            result_df <- result_df |>
                collapse::fgroup_by(group_vars) |>
                collapse::fsummarise(across(values, collapse::fsum))

            monitor_df <- monitor_df |> monitor_end()

            result_list <- list(result_df, monitor_df)

            fast_pct <- TRUE
        }
        else{
            # Apply formats first
            result_df <- data_frame |> apply_format(formats, group_vars)

            monitor_df <- monitor_df |> monitor_end()

            # If there are multiple evaluation types given, every variable gets an
            # extension to it's variable name
            # If there is only one evaluation type given, the original variable names
            # are kept without extension
            result_list <- result_df |>
                matrix_summarise(values,
                                 group_vars,
                                 result_df[[weight_var]],
                                 valid_stats,
                                 list_of_statistics,
                                 monitor_df,
                                 !flag_shortcut)

            fast_pct <- FALSE
        }

        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        #  Compute percentages
        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        result_list <- compute_group_percentages(data_frame,
                                                 result_list[[1]],
                                                 statistics,
                                                 group_vars,
                                                 formats,
                                                 values,
                                                 weight_var,
                                                 list_of_statistics,
                                                 result_list[[2]],
                                                 fast_pct)

        result_list <- compute_total_percentages(data_frame,
                                                 result_list[[1]],
                                                 statistics,
                                                 group_vars,
                                                 values,
                                                 weight_var,
                                                 list_of_statistics,
                                                 result_list[[2]],
                                                 fast_pct)

        # Split results and monitor
        result_df  <- result_list[[1]]
        monitor_df <- result_list[[2]]

        #---------------------------------------------------------------------#
        monitor_df <- monitor_df |> monitor_start("Clean up", "Clean up")
        #---------------------------------------------------------------------#

        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        #  Clean up
        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        # Generate TYPE variables
        type <- paste(group_vars, collapse = "+")
        result_df[["TYPE"]]    <- type
        result_df[["TYPE_NR"]] <- 1
        result_df[["DEPTH"]]   <- length(group_vars)

        # Reorder new variables in order of requested statistics
        result_df <- result_df |>
            handle_sum_drops(flag_remove_sum, flag_remove_sum_wgt) |>
            reorder_summarised_columns(requested)

        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        # If no formats are used (meaning only pre defined variables) it is
        # possible to merge the summarized variables back to the original
        # data frame in one go.
        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        if (merge_back){
            print_step("MAJOR", "Merging back")
            #-----------------------------------------------------------------#
            monitor_df <- monitor_df |> monitor_next("Merge back", "Merge back")
            #-----------------------------------------------------------------#

            # Don't merge back type variables, only summarised variable
            result_df <- result_df |> drop_type_vars()

            result_df <- original_df |>
                collapse::fungroup() |>
                collapse::join(result_df,
                               on      = group_vars,
                               how     = "left",
                               verbose = FALSE,
                               overid  = 2) |>
                dropp(".temp_weight")
        }
        # If formats are applied and all format categories schould be output
        else if (print_miss && !is.null(formats)){
            result_df <- result_df |> print_missing(formats, group_vars)
        }

    }
    # If every possible combination of the given grouping variables should be evaluated
    else if (tolower(nesting) %in% c("all", "single")){
        all_results <- list()
        index <- 1

        print_step("MAJOR", "Executing merge:")
        print_step("MINOR", "total")

        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        # The results of all the possible combinations are computed one after
        # another starting with the grand total (ungrouped)
        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        if (flag_shortcut){
            # Create a new original data frame to be able to remove NA values
            original_df_shortcut <- data_frame

            # Final summarise
            total_df <- data_frame |>
                collapse::fsummarise(across(values, collapse::fsum))

            sum_columns   <- values[grepl("_sum$", values)]
            new_group_pct <- paste0(gsub("_sum$", "", sum_columns), "_pct_group")
            new_total_pct <- paste0(gsub("_sum$", "", sum_columns), "_pct_total")

            monitor_df <- monitor_df |> monitor_end()

            total_list <- list(total_df, monitor_df)
        }
        else{
            monitor_df <- monitor_df |> monitor_end()

            total_list <- data_frame |>
                collapse::fungroup() |>
                dropp(class) |>
                matrix_summarise(values,
                                 NULL,
                                 data_frame[[weight_var]],
                                 valid_stats,
                                 list_of_statistics,
                                 monitor_df,
                                 !flag_shortcut)

            sum_columns   <- paste0(values, "_sum")
            new_group_pct <- paste0(values, "_pct_group")
            new_total_pct <- paste0(values, "_pct_total")
        }

        # Split results and monitor
        total_df   <- total_list[[1]]
        monitor_df <- total_list[[2]]

        #---------------------------------------------------------------------#
        monitor_df <- monitor_df |> monitor_start("pct_group(total)", "Calc(total)")
        #---------------------------------------------------------------------#

        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        # Total percentages
        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        if (any(c("pct_group", "pct_total") %in% statistics)){
            # Copy total sums to have them ready for the group data frames. This way
            # they can be easily joined to compute total percentages faster.
            new_columns <- paste0(sum_columns, "_qol")

            total_df_copy <- data.table::copy(total_df) |>
                keep(sum_columns) |>
                collapse::frename(stats::setNames(new_columns, sum_columns))
        }

        # Compute percentages
        if ("pct_group" %in% statistics){
            # For compute percentages for every variable
            for (i in seq_along(new_group_pct)){
                current_new_var <- new_group_pct[i]

                total_df[[current_new_var]] <- 100
            }
        }

        #---------------------------------------------------------------------#
        monitor_df <- monitor_df |> monitor_next("pct_total(total)", "Calc(total)")
        #---------------------------------------------------------------------#

        if ("pct_total" %in% statistics){
            # For compute percentages for every variable
            for (i in seq_along(new_total_pct)){
                current_new_var <- new_total_pct[i]

                total_df[[current_new_var]] <- 100
            }
        }

        # Every grouping variable which was not part of the current grouping
        # gets set to a missing value
        total_df[group_vars]  <- NA
        total_df[["TYPE"]]    <- "total"
        total_df[["TYPE_NR"]] <- as.integer(index)
        total_df[["DEPTH"]]   <- as.integer(0)

        # Add data frame to list to add them together at the end
        all_results[["total"]] <- total_df

        monitor_df <- monitor_df |> monitor_end()

        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        # If grouping variables where defined: compute every possible combination
        # of these variables. Starting with single combinations, then double,
        # triple, and so on.
        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        if (length(group_vars) > 0){

            index <- index + 1

            print_step("MAJOR", "Executing combination merge:")

            for (i in seq_along(group_vars)){
                # Break loop early if the maximum depth of combinations is surpassed.
                # This only takes effect if types are selected.
                if (i > max_combination_depth){
                    break
                }

                # Generate every possible combination
                combinations <- utils::combn(group_vars, i, simplify = FALSE)

                # If only combinations of depth 0 and 1 should be generated break out of the loop
                if (tolower(nesting) == "single" && i > 1){
                    break
                }

                #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                # Loop through combinations
                #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

                for (combination in combinations){
                    # If types are specified check first if combination is of the right type
                    if (length(types) > 0){
                        #-----------------------------------------------------#
                        monitor_df <- monitor_df |>
                            monitor_start(paste0("Drop Combo(", paste(combination, collapse = " + "), ")"),
                                          "Drop Combo")
                        #-----------------------------------------------------#

                        # Order current combination alphabetically
                        reordered_combo <- paste(sort(combination), collapse = "+")

                        # Check if current combination is part of the specified types.
                        # If not jump to next combination.
                        if (!reordered_combo %in% reordered_types){
                            next
                        }

                        monitor_df <- monitor_df |> monitor_end()
                    }

                    #---------------------------------------------------------#
                    monitor_df <- monitor_df |>
                        monitor_start(paste0("Formats(", paste(combination, collapse = " + "), ")"),
                                      paste0("Formats(", paste(combination, collapse = " + "), ")"))
                    #---------------------------------------------------------#

                    # Remove NAs from grouping variables. This has to be done individually, because
                    # if otherwise done before in general, too many observations would be removed.
                    # E.g. two of three variables don't have NA values, then the removal of NA values in
                    # the third variable would also effect the first two variables. To get the maximum
                    # out of each combination this is done for every combination separately.
                    if (na.rm){
                        if (flag_shortcut){
                            data_frame <- original_df_shortcut[!collapse::missing_cases(original_df_shortcut[combination]), ]
                        }
                        else{
                            data_frame <- original_df[!collapse::missing_cases(original_df[combination]), ]
                        }
                    }

                    print_step("MINOR", paste(combination, collapse = " + "))

                    get_group_missings(original_df[combination], notes, na.rm)

                    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                    # Shortcut crossroad
                    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

                    if (flag_shortcut){
                        # Final summarise with formatted data frame
                        group_df <- data_frame |>
                            collapse::fgroup_by(combination) |>
                            collapse::fsummarise(across(values, collapse::fsum)) |>
                            apply_format(formats, combination)

                        # Final summarise with formatted data frame
                        group_df <- group_df |>
                            collapse::fgroup_by(combination) |>
                            collapse::fsummarise(across(values, collapse::fsum))

                        monitor_df <- monitor_df |> monitor_end()

                        group_list <- list(group_df, monitor_df)

                        fast_pct <- TRUE
                    }
                    else{
                        # Apply formats first
                        group_df <- data_frame |>
                            collapse::fselect(combination, values, weight_var) |>
                            apply_format(formats, combination)

                        monitor_df <- monitor_df |> monitor_end()

                        # If there are multiple evaluation types given, every variable gets an
                        # extension to it's variable name
                        # If there is only one evaluation type given, the original variable names
                        # are kept without extension
                        group_list <- group_df |>
                            matrix_summarise(values,
                                             combination,
                                             group_df[[weight_var]],
                                             valid_stats,
                                             list_of_statistics,
                                             monitor_df,
                                             !flag_shortcut)

                        fast_pct <- FALSE
                    }

                    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                    # Compute percentages
                    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

                    if (length(types) == 0){
                        # Single variable combinations
                        if (length(combination) == 1){
                            if (!na.rm){
                                # If there is only one grouping variable the super group will be
                                # NULL and therefore can't be grouped. The super total therefore
                                # is simply the grand total.
                                group_list <- compute_total_percentages_short(group_list[[1]],
                                                                              total_df_copy,
                                                                              statistics,
                                                                              sum_columns,
                                                                              combination,
                                                                              "pct_group",
                                                                              group_list[[2]])
                            }
                            else{
                                # If NA values should be removed, the group percentages are computed
                                # as usual so that each variable can be 100 % individually.
                                group_list <- compute_group_percentages(data_frame,
                                                                        group_list[[1]],
                                                                        statistics,
                                                                        combination,
                                                                        formats,
                                                                        values,
                                                                        weight_var,
                                                                        list_of_statistics,
                                                                        group_list[[2]],
                                                                        fast_pct)
                            }
                        }
                        # Combinations of more than one variable
                        else{
                            if (!na.rm){
                                # Evaluate the group percentages based on the super group.
                                # Since the depending super group is already computed
                                # in one of the previous iterations we can make use of
                                # that and simply join the super_df accordingly.
                                super_group <- paste(combination[-length(combination)], collapse = "+")

                                group_list <- compute_group_percentages_short(group_list[[1]],
                                                                              all_results[[super_group]],
                                                                              statistics,
                                                                              combination,
                                                                              sum_columns,
                                                                              group_list[[2]],
                                                                              fast_pct)
                            }
                            else{
                                # If NA values should be removed, the group percentages are computed
                                # as usual so that each variable combination can be 100 % individually.
                                group_list <- compute_group_percentages(data_frame,
                                                                        group_list[[1]],
                                                                        statistics,
                                                                        combination,
                                                                        formats,
                                                                        values,
                                                                        weight_var,
                                                                        list_of_statistics,
                                                                        group_list[[2]],
                                                                        fast_pct)
                            }
                        }
                    }
                    # When types are specified
                    else{
                        group_list <- compute_group_percentages(data_frame,
                                                                group_list[[1]],
                                                                statistics,
                                                                combination,
                                                                formats,
                                                                values,
                                                                weight_var,
                                                                list_of_statistics,
                                                                group_list[[2]],
                                                                fast_pct)
                    }

                    group_list <- compute_total_percentages_short(group_list[[1]],
                                                                  total_df_copy,
                                                                  statistics,
                                                                  sum_columns,
                                                                  combination,
                                                                  "pct_total",
                                                                  group_list[[2]])

                    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                    # Clean up
                    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

                    # Split results and monitor
                    group_df   <- group_list[[1]]
                    monitor_df <- group_list[[2]]

                    #---------------------------------------------------------#
                    monitor_df <- monitor_df |>
                        monitor_start(paste0("Finish(", paste(combination, collapse = " + "), ")"),
                                      paste0("Finish(", paste(combination, collapse = " + "), ")"))
                    #---------------------------------------------------------#

                    # Every grouping variable which was not part of the current grouping
                    # gets set to a missing value
                    missing_vars <- setdiff(group_vars, combination)
                    group_df[missing_vars] <- NA
                    type <- paste(combination, collapse = "+")
                    group_df[["TYPE"]]    <- type
                    group_df[["TYPE_NR"]] <- as.integer(index)
                    group_df[["DEPTH"]]   <- as.integer(i)

                    # If formats are applied and all format categories schould be output
                    if (print_miss && !is.null(formats)){
                        group_df <- group_df |> print_missing(formats, combination)
                    }

                    # Add data frame to list to add them together at the end
                    all_results[[type]] <- group_df

                    index <- index + 1

                    monitor_df <- monitor_df |> monitor_end()
                }
            }
        }

        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        # Additional clean up
        #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        print_step("MAJOR", "Putting results together")

        #---------------------------------------------------------------------#
        monitor_df <- monitor_df |> monitor_start("Clean up", "Clean up")
        #---------------------------------------------------------------------#

        # Put all computed data frames one below the other and sort the variables
        # in the order: groups -> types -> results
        result_df <- data.table::rbindlist(all_results, fill = TRUE)

        # Reorder new variables in order of requested statistics
        result_df <- result_df |>
            data.table::setcolorder(c(group_vars, "TYPE", "TYPE_NR", "DEPTH")) |>
            handle_sum_drops(flag_remove_sum, flag_remove_sum_wgt) |>
            reorder_summarised_columns(requested)

        # Automatically fuse variables of depth 0 and 1 if only single nested combinations
        # were generated
        if (tolower(nesting) == "single"){
            result_df <- result_df |>
                fuse_variables("fused_vars", group_vars)
        }

        # If types are defined, remove total if not defined in types
        if (length(types) > 0 && !"total" %in% types){
            result_df <- result_df |> collapse::fsubset(TYPE != "total")
        }
    }

    #---------------------------------------------------------------------#
    monitor_df <- monitor_df |> monitor_start("Clean up", "Clean up")
    #---------------------------------------------------------------------#

    # If only specific variables should be kept per statistic, clean up the additional
    # not needed variables.
    if (!is.null(vars_per_stat_list)){
        vars_to_keep <- c()

        # Transform statistics list into a vector of variable names
        for (name in names(vars_per_stat_list)){
            vars_to_keep <- c(vars_to_keep, paste0(vars_per_stat_list[[name]], "_", name))
        }

        # Get all produced statistic extensions
        stat_extensions <- paste0("_", names(vars_per_stat_list), "$", collapse = "|")

        # Get all statistics variables and exclude the ones to keep to get a
        # vector of variable names to drop.
        statistic_variables <- grep(stat_extensions, names(result_df), value = TRUE)
        vars_to_drop        <- statistic_variables[!statistic_variables %in% vars_to_keep]

        result_df <- suppressMessages(result_df |> dropp(vars_to_drop))
    }

    # Drop pseudo group variable if there is one
    if (any(class == "pseudo_class")){
        result_df <- result_df |> dropp(class)
    }

    #---------------------------------------------------------------------#
    monitor_df |> monitor_plot(by = monitor_grouping, draw_plot = monitor)
    #---------------------------------------------------------------------#

    if (flag_shortcut){
        print_closing(3)
    }
    else{
        print_closing(10)
    }

    result_df
}

###############################################################################
# List of all possible actions
###############################################################################
static_statistics <- list(sum      = collapse::fsum,
                          sum_wgt  = collapse::fsum,
                          freq     = function(x, w, g) collapse::fnobs(x, g = g),
                          freq_g0  = function(x, w, g) freq_g0_qol(x, group = g),
                          mean     = collapse::fmean,
                          median   = collapse::fmedian,
                          mode     = collapse::fmode,
                          min      = function(x, w, g) collapse::fmin(x, g = g),
                          max      = function(x, w, g) collapse::fmax(x, g = g),
                          sd       = collapse::fsd,
                          variance = collapse::fvar,
                          first    = function(x, w, g) collapse::ffirst(x, g = g),
                          last     = function(x, w, g) collapse::flast(x, g = g),
                          missing  = function(x, w, g) collapse::fsum(data.table::as.data.table(is.na(x)), g = g))


#' Compute Percentile Functions
#'
#' @description
#' Computes percentile functions and adds them to the list of statistical functions.
#'
#' @param statistics User provided statistics.
#'
#' @return
#' Returns a complete list of named statistical functions.
#'
#' @noRd
get_complete_statistics_list <- function(statistics){
    all_stats <- static_statistics

    for (stat_name in statistics){
        # Match pattern p<number>
        if (grepl("^p\\d+$", stat_name)){
            prob <- as.numeric(sub("^p", "", stat_name)) / 100

            if (prob > 1){
                print_message("WARNING", "Percentiles are only possible from p0 to p100. [stat_name] will be omitted.",
                              stat_name = stat_name, always_print = TRUE)
                next
            }

            # Define the function
            all_stats[[stat_name]] <- (function(prob){
                force(prob)
                function(x, w = NULL, g = NULL){
                    percentiles_qol(x, w, g, probs = prob)
                }
            })(prob)
        }
    }

    all_stats
}


###############################################################################
# Turns data frame into matrix for fast computation. Evaluates statistics and
# turns everything back into a data frame.
###############################################################################
#' Core Summarise
#'
#' @description
#' This is the core summarisation process. The input data frame is split up in a
#' matrix of values and the grouping variables to make summarisation faster. At
#' the end everything is put back together as a data table.
#'
#' @param data_frame The data frame to be summarised.
#' @param values A vector containing all variables that should be summarised.
#' @param group_vars A vector containing all grouping variables.
#' @param weight Weight variable for weighted results.
#' @param statistics User specified statistics.
#' @param list_of_statistics A list containing all possible statistic functions.
#' @param monitor_df Data frame which stores the monitoring values.
#'
#' @return
#' Returns a list with the summarised data table and the monitoring data frame.
#'
#' @noRd
matrix_summarise <- function(data_frame,
                             values,
                             group_vars,
                             weight,
                             statistics,
                             list_of_statistics,
                             monitor_df,
                             flag_shortcut = TRUE){

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Preparation
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    #-------------------------------------------------------------------------#
    monitor_df <- monitor_df |> monitor_start("Matrix conversion", paste0("Calc(", paste(group_vars, collapse = " + "), ")"))
    #-------------------------------------------------------------------------#

    # Store original types of grouping variables to later reapply them.
    original_types <- lapply(data_frame[group_vars], class)

    # Create group
    if (is.null(group_vars)){
        # In case there is no grouping (e.g. total percentages) create pseudo group
        group_vars <- "pseudo_group"
        data_frame[[group_vars]] <- 1
        grouping <- collapse::GRP(data_frame, group_vars, sort = flag_shortcut)

    }
    else{
        grouping <- collapse::GRP(data_frame, group_vars, sort = flag_shortcut)
    }

    # Extract group and value columns
    result_df <- data.table::as.data.table(grouping[["groups"]])
    value_df  <- data_frame |> collapse::fselect(values)

    monitor_df <- monitor_df |> monitor_end()

    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # Compute statistics
    #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    # Remove percentages from the statistics vector because they are handled
    # differently than the other single statistics.
    statistics <- grep("^pct", statistics, value = TRUE, invert = TRUE)

    for (single_stat in statistics){
        #-----------------------------------------------------------------#
        monitor_df <- monitor_df |> monitor_start(single_stat, paste0("Calc(", paste(group_vars, collapse = " + "), ")"))
        #-----------------------------------------------------------------#

        stat_function <- list_of_statistics[[single_stat]]

        # All functions are handled the same and called from the statistic function
        # list.
        if (single_stat != "sum_wgt"){
            stat_result <- stat_function(value_df, w = weight, g = grouping)

            result_df[, c(paste0(values, "_", single_stat))] <- stat_result
        }
        # With one exception. The sum of weights.
        else{
            stat_result <- data.table::as.data.table(weight) |>
                collapse::fsum(g = grouping) |>
                collapse::frename("weight" = "sum_wgt")

            result_df <- cbind(result_df, stat_result)
        }

        monitor_df <- monitor_df |> monitor_end()
    }

    #-----------------------------------------------------------------#
    monitor_df <- monitor_df |> monitor_start("Restore types", "Restore types")
    #-----------------------------------------------------------------#

    # Drop pseudo group, if no grouping variables where specified
    if ("pseudo_group" %in% names(result_df)){
        result_df <- result_df |> collapse::fselect(-pseudo_group)
    }

    # Restore original variable types of numeric variables
    for (variable in names(original_types)){
        var_type <- original_types[[variable]]

        if (length(var_type) > 1){
            next
        }

        if (var_type == "integer"){
            result_df[[variable]] <- as.integer(result_df[[variable]])
        }
        else if (var_type == "double"){
            result_df[[variable]] <- as.double(result_df[[variable]])
        }
        else if (var_type == "numeric"){
            result_df[[variable]] <- as.numeric(result_df[[variable]])
        }
    }

    monitor_df <- monitor_df |> monitor_end()

    list(result_df, monitor_df)
}

###############################################################################
# Handle what happens with automatically generated sum variables
###############################################################################
#' Drop Sum Stats
#'
#' @description
#' Drops sum and sum_wgt variables if not specified by the user.
#'
#' @param result_df The result data frame which contains the sum variables.
#' @param statistics The user provided statistics to check whether sum stats are
#' provided.
#'
#' @return
#' Returns an adjusted result data frame.
#'
#' @noRd
handle_sum_drops <- function(result_df, flag_sum, flag_sum_wgt){
    # Handle sum and sum of weights
    if (flag_sum_wgt){
        result_df <- result_df |> dropp("sum_wgt")
    }

    sum_columns <- grep("_sum$", names(result_df), value = TRUE)

    if (flag_sum){
        result_df <- result_df |> dropp(sum_columns)
    }

    result_df
}

###############################################################################
# After summarise the new variables are ordered like the list_of_statistics.
# This function reorders them in the order which the user has entered on
# summarise_plus function call.
###############################################################################
#' Order Columns by Stats
#'
#' @description
#' Order the value columns of the final result data frame by stats as provided
#' by the user.
#'
#' @param data_frame The result data frame which contains all variables.
#' @param requested_stats The user provided statistics to get the order from.
#'
#' @return
#' Returns a reordered data frame.
#'
#' @noRd
reorder_summarised_columns <- function(data_frame, requested_stats){
    # Select all summarised variables in blocks of the requested statistics
    ordered_cols <- unlist(lapply(requested_stats, function(single_stat){
        # Concatenates the variable ending like "_sum", "_mean", etc. and selects
        # all variables within the given data frame and returns them
        grep(paste0(single_stat, "$"),
             names(data_frame),
             value = TRUE)
    }))

    ordered_cols <- collapse::funique(ordered_cols)

    # Put the ordered columns at the end of the data frame
    data_frame |> data.table::setcolorder(ordered_cols, after = collapse::fncol(data_frame))
}

###############################################################################
# Handle what happens with missing categories, if they appear in a format
###############################################################################
#' Output Missing Values If They Are Present In A Format
#'
#' @description
#' Sometimes it can be handy to not only output all categories which are present
#' in a data frame but also additionally all categories which could have been
#' potentialy present. A format can provide more categories which are NA in the
#' data frame, this functions outputs them as NA rows.
#'
#' @param data_frame The result data frame which contains all formatte variables.
#' @param formats The provided formats.
#' @param group_vars All grouping variables.
#'
#' @return
#' Returns a data frame with added missing categories.
#'
#' @noRd
print_missing <- function(data_frame,
                          formats,
                          group_vars){
    format_list <- formats

    # First check if given variables to format are part of the data frame. This only
    # can be the case if "formats" is provided but the variables are missing in the
    # "class" parameter. In this case abort, because there shouldn't be extra categories,
    # which shouldn't be there in the first place.
    format_names <- names(formats)
    format_vars  <- group_vars[group_vars %in% format_names]

    if (length(format_vars) == 0){
        return(data_frame)
    }

    # Determine which variables are actually formatted in the data frame and which
    # have to be added unformatted.
    no_format_vars <- group_vars[!group_vars %in% format_names]

    # Make sure only variables stay in the format list which are part of the group variables.
    # There is a missmatch, when generating all combinations. Some combinations have lesser
    # grouping variables than formats are provided.
    format_select <- group_vars

    if (length(no_format_vars) > 0){
        format_select <- format_select[!group_vars %in% no_format_vars]
    }

    format_list <- format_list[format_select]

    # Normally I would check if there are missing categories at all and if not abort.
    # I think this has no benefit here and only costs time, since I am only using this
    # function as intended. I leave this comment here, to know what is missing, if a
    # case comes up for which this check would be neccessary.

    # Every unformatted variable is put into a list as single data frame with only
    # its unique values. Only if unformatted variables are present.
    if (length(no_format_vars) > 0){
        no_format_list <- lapply(data_frame |> keep(no_format_vars),
                                 function(variable){
                                     no_format_df        <- data.table::as.data.table(collapse::funique(variable))
                                     names(no_format_df) <- "label"

                                     no_format_df
                                 })

        format_list <- c(format_list, no_format_list)
    }

    # Create the cartesian product of all given formats to generate all possible
    # categories.
    format_df <- suppressMessages(expand_formats(format_list)) |>
        data.table::setcolorder(group_vars) |>
        data.table::setorderv(group_vars)

    # Generate pseudo variable to join
    format_df[[".pseudo_join"]] <- 1

    # Get values from automatically generated variables to add them back for NA
    # values after the join
    type_value    <- data_frame[1, "TYPE"]
    type_nr_value <- data_frame[1, "TYPE_NR"]
    depth_value   <- data_frame[1, "DEPTH"]

    # Perform left join on all format categories, so that each possible category
    # appears in the final data frame. Additionally take over the values from the
    data_frame <- suppressMessages(
        collapse::join(format_df, data_frame,
                       on       = group_vars,
                       how      = "left",
                       multiple = TRUE,
                       verbose  = FALSE,
                       overid   = 2) |>
            dropp(".pseudo_join"))

    # Re apply automatically generated variables to get rid of NA values
    data_frame[["TYPE"]]    <- type_value
    data_frame[["TYPE_NR"]] <- type_nr_value
    data_frame[["DEPTH"]]   <- depth_value

    data_frame
}
