R/help_plot.R

Defines functions .get_plot_style_config .get_export_config .handle_plot_title_and_export .display_export_dimensions .apply_plot_style_config .calculate_font_sizes .calculate_plot_style_config .create_color_palette .generate_stack_colors .calculate_stack_limits .create_single_unstacked_plot .create_single_stacked_plot .generate_color_palette .categorize_values_by_panel .create_single_detail_plot .generate_comparison_colors .create_single_comparison_plot .export_plot_output .filter_top_impact_values_stack .calculate_stack_totals .filter_top_impact_values_detail .calculate_plot_dimensions .calculate_panel_layout .format_variable_names .check_unit_column .prepare_data_source .process_filter_var .find_column .calculate_value_axis_limits .set_axis_labels .format_y_axis_label .format_axis_labels .add_zero_line .create_facet_wrap .apply_axis_scale .finalize_plot_export .create_plots_with_panels .generate_plots .preprocess_data

# SIMPLIFIED COMMON HELPERS -------------------------------------------------

#' @title Preprocess Data for Plotting
#' @description
#' Unified function to validate columns, handle filtering, process unit and split-by logic,
#' and format variable names prior to plotting. Used across all GTAPViz plot types.
#'
#' @param data A data frame or list of data frames containing GTAP results.
#' @param x_axis_from Character. X-axis category column (e.g., "REG", "Sector").
#' @param split_by Character or NULL. Column(s) for splitting plots.
#' @param panel_var Character. Facet variable (typically "Experiment").
#' @param variable_col Character. Column identifying variables.
#' @param unit_col Character. Column identifying unit information.
#' @param desc_col Character. Column identifying variable descriptions.
#' @param filter_var Vector or data frame to filter variable values.
#' @param var_name_by_description Logical. Whether to use descriptions in titles.
#' @param add_var_info Logical. Whether to append variable codes in titles.
#' @param stack_value_from Character or NULL. Used only in stack plots.
#'
#' @return A list with preprocessed `data` and `is_macro_mode` flag.
#' @keywords internal
#' @noRd
.preprocess_data <- function(data, x_axis_from, split_by, panel_var, variable_col,
                             unit_col, desc_col, filter_var, var_name_by_description,
                             add_var_info, stack_value_from = NULL) {
  # Validate columns with better error handling
  if (!is.null(stack_value_from)) {
    data <- .prepare_data_source(data, x_axis_from, stack_value_from, variable_col)
  } else {
    data <- .prepare_data_source(data, x_axis_from, variable_col = variable_col)
  }

  # Handle unit column
  unit_result <- .check_unit_column(data, unit_col)
  data <- unit_result$data
  unit_col <- unit_result$unit_col

  # Process split_by
  is_macro_mode <- is.null(split_by) || (is.logical(split_by) && !split_by)

  if (!is_macro_mode) {
    # Validate split_by columns
    if (length(split_by) > 1) {
      # Check if all split_by columns exist
      missing_cols <- setdiff(split_by, names(data))
      if (length(missing_cols) > 0) {
        stop(paste0("Split-by column(s) not found in data: ", paste(missing_cols, collapse=", "),
                    ". Available columns are: ", paste(names(data), collapse=", ")))
      }

      # Create a split_display for multiple columns
      data$split_display <- apply(data[, split_by, drop = FALSE], 1, paste, collapse = "-")
    } else {
      # Check if split_by column exists
      if (!split_by %in% names(data)) {
        stop(paste0("Split-by column '", split_by, "' not found in data. ",
                    "Available columns are: ", paste(names(data), collapse=", ")))
      }
    }
  }

  # Filter data using enhanced filter function
  filtered_data <- .process_filter_var(data, filter_var, variable_col)

  # Early return if filtering resulted in empty data
  if (nrow(filtered_data) == 0) {
    warning("No data remains after filtering. Please check your filter criteria.")
    return(NULL)
  }

  # Format variable names
  if (variable_col %in% names(filtered_data) && desc_col %in% names(filtered_data)) {
    filtered_data <- .format_variable_names(
      filtered_data, variable_col, desc_col, var_name_by_description, add_var_info
    )
  }

  # Ensure panel_var is a factor with original order
  if (panel_var %in% names(filtered_data)) {
    panel_levels <- unique(filtered_data[[panel_var]])
    filtered_data[[panel_var]] <- factor(filtered_data[[panel_var]], levels = panel_levels)
  } else {
    warning(paste0("Panel variable '", panel_var, "' not found in data. ",
                   "This might affect facet layouts. Available columns are: ",
                   paste(names(filtered_data), collapse=", ")))
  }

  return(list(
    data = filtered_data,
    is_macro_mode = is_macro_mode
  ))
}

#' @title Generate All Plots from GTAP Data
#' @description
#' Creates plots for each unit and split-by group using the specified plot type.
#' Supports top-impact filtering, macro modes, and dispatching to comparison/detail/stack variants.
#'
#' @param data A data frame containing GTAP results.
#' @param unit_col Character. Unit column name.
#' @param panel_var Character. Facet variable column.
#' @param x_axis_from Character. Column used for x-axis categories.
#' @param separate_figure Logical. Whether to separate plots by panel.
#' @param style_config List. Plot styling configuration.
#' @param is_macro_mode Logical. Whether macro-level plots are requested.
#' @param plot_type Character. One of "comparison", "detail", "stack", "unstack".
#' @param invert_axis Logical. Whether to flip bar orientation.
#' @param variable_col Character or NULL. Column for variable names.
#' @param split_by Character or NULL. Column(s) used for plot splitting.
#' @param top_impact Numeric or NULL. Number of top values to keep.
#' @param stack_value_from Character or NULL. Column for stack component categories.
#' @param show_total Logical. Whether to show total values in stacked bars.
#' @param unstack_plot Logical. Whether to unstack bars (only for stack type).
#'
#' @return A named list of ggplot2 plot objects.
#' @keywords internal
#' @noRd
.generate_plots <- function(data, unit_col, panel_var, x_axis_from, separate_figure,
                            style_config, is_macro_mode, plot_type, invert_axis,
                            variable_col = NULL, split_by = NULL, top_impact = NULL,
                            stack_value_from = NULL, show_total = FALSE, unstack_plot = FALSE) {

  plot_list <- list()
  unit_groups <- split(data, data[[unit_col]])

  for (unit_name in names(unit_groups)) {
    unit_data <- unit_groups[[unit_name]]

    # Apply top impact filtering if needed
    # Apply top impact filtering if needed
    if (!is.null(top_impact)) {
      if (plot_type == "detail") {
        # Determine the proper group column
        if (!is_macro_mode && length(split_by) > 1) {
          unit_data$._split_group_ <- apply(unit_data[, split_by, drop = FALSE], 1, paste, collapse = "-")
          top_impact_filter_col <- "._split_group_"
        } else if (!is_macro_mode) {
          top_impact_filter_col <- split_by
        } else {
          top_impact_filter_col <- x_axis_from
        }

        unit_data <- .filter_top_impact_values_detail(
          data = unit_data,
          top_impact = top_impact,
          group_col = top_impact_filter_col,
          panel_var = panel_var,
          x_axis_from = x_axis_from,
          variable_col = variable_col,
          unit_col = unit_col
        )

        # Clean up temporary column if it exists
        if ("._split_group_" %in% names(unit_data)) {
          unit_data$._split_group_ <- NULL
        }
      } else if (plot_type == "stack") {
        total_data <- .calculate_stack_totals(unit_data, x_axis_from, panel_var)
        unit_data <- .filter_top_impact_values_stack(unit_data, total_data, top_impact, is_macro_mode,
                                                     split_by, x_axis_from, panel_var, variable_col,
                                                     unit_col)
      }
    }

    # FIX: Handle macro mode properly (split_by = NULL)
    if (is_macro_mode) {
      # In macro mode, we process a single dataset with all data
      if (plot_type == "detail") {
        # For detail plots, we need to process by variable
        var_combinations <- unique(unit_data[[variable_col]])

        for (var_name in var_combinations) {
          var_data <- unit_data[unit_data[[variable_col]] == var_name, ]

          if (separate_figure) {
            panel_values <- unique(var_data[[panel_var]])

            for (panel_val in panel_values) {
              panel_data <- var_data[var_data[[panel_var]] == panel_val, ]

              title_info <- .handle_plot_title_and_export(
                var_name = var_name,
                sep_value = NULL,
                plot_type = "detail",
                is_macro_mode = TRUE,
                variable_col = variable_col,
                unit_name = unit_name,
                style_config = style_config,
                data = panel_data,
                separate_figure = TRUE,
                panel_val = panel_val
              )

              p <- .create_single_detail_plot(
                data = panel_data,
                x_axis_from = x_axis_from,
                plot_title = title_info$title,
                unit = unit_name,
                panel_rows = style_config$panel_rows,
                panel_cols = style_config$panel_cols,
                panel_var = panel_var,
                invert_axis = invert_axis,
                top_impact = top_impact,
                plot_style_config = style_config
              )

              plot_list[[title_info$export_name]] <- p
            }
          } else {
            title_info <- .handle_plot_title_and_export(
              var_name = var_name,
              plot_type = "detail",
              is_macro_mode = TRUE,
              variable_col = variable_col,
              unit_name = unit_name,
              style_config = style_config,
              data = var_data
            )

            p <- .create_single_detail_plot(
              data = var_data,
              x_axis_from = x_axis_from,
              plot_title = title_info$title,
              unit = unit_name,
              panel_rows = style_config$panel_rows,
              panel_cols = style_config$panel_cols,
              panel_var = panel_var,
              invert_axis = invert_axis,
              top_impact = top_impact,
              plot_style_config = style_config
            )

            plot_list[[title_info$export_name]] <- p
          }
        }
      } else if (plot_type == "stack") {
        # For stack plots in macro mode
        total_data <- .calculate_stack_totals(unit_data, x_axis_from, panel_var)

        if (unstack_plot) {
          # Handle unstacked plots in macro mode
          x_axis_values <- unique(unit_data[[x_axis_from]])

          for (x_val in x_axis_values) {
            x_data <- unit_data[unit_data[[x_axis_from]] == x_val, ]
            x_totals <- total_data[total_data[[x_axis_from]] == x_val, ]

            if (separate_figure) {
              panel_values <- unique(x_data[[panel_var]])

              for (panel_val in panel_values) {
                panel_x_data <- x_data[x_data[[panel_var]] == panel_val, ]
                panel_x_totals <- x_totals[x_totals[[panel_var]] == panel_val, ]

                title_info <- .handle_plot_title_and_export(
                  var_name = NULL,
                  sep_value = NULL,
                  x_value = x_val,
                  plot_type = "unstack",
                  is_macro_mode = TRUE,
                  variable_col = variable_col,
                  unit_name = unit_name,
                  style_config = style_config,
                  data = panel_x_data,
                  separate_figure = TRUE,
                  panel_val = panel_val
                )

                p <- .create_single_unstacked_plot(
                  data = panel_x_data,
                  total_data = panel_x_totals,
                  x_axis_from = x_axis_from,
                  stack_value_from = stack_value_from,
                  plot_title = title_info$title,
                  unit = unit_name,
                  panel_rows = style_config$panel_rows,
                  panel_cols = style_config$panel_cols,
                  panel_var = panel_var,
                  invert_axis = invert_axis,
                  top_impact = top_impact,
                  plot_style_config = style_config
                )

                plot_list[[title_info$export_name]] <- p
              }
            } else {
              title_info <- .handle_plot_title_and_export(
                var_name = NULL,
                sep_value = NULL,
                x_value = x_val,
                plot_type = "unstack",
                is_macro_mode = TRUE,
                variable_col = variable_col,
                unit_name = unit_name,
                style_config = style_config,
                data = x_data
              )

              p <- .create_single_unstacked_plot(
                data = x_data,
                total_data = x_totals,
                x_axis_from = x_axis_from,
                stack_value_from = stack_value_from,
                plot_title = title_info$title,
                unit = unit_name,
                panel_rows = style_config$panel_rows,
                panel_cols = style_config$panel_cols,
                panel_var = panel_var,
                invert_axis = invert_axis,
                top_impact = top_impact,
                plot_style_config = style_config
              )

              plot_list[[title_info$export_name]] <- p
            }
          }
        } else {
          # Regular stacked plots in macro mode
          if (separate_figure) {
            panel_values <- unique(unit_data[[panel_var]])

            for (panel_val in panel_values) {
              panel_data <- unit_data[unit_data[[panel_var]] == panel_val, ]
              panel_totals <- total_data[total_data[[panel_var]] == panel_val, ]

              title_info <- .handle_plot_title_and_export(
                var_name = NULL,
                sep_value = NULL,
                plot_type = "stack",
                is_macro_mode = TRUE,
                variable_col = variable_col,
                unit_name = unit_name,
                style_config = style_config,
                data = panel_data,
                separate_figure = TRUE,
                panel_val = panel_val
              )

              p <- .create_single_stacked_plot(
                data = panel_data,
                total_data = panel_totals,
                x_axis_from = x_axis_from,
                stack_value_from = stack_value_from,
                plot_title = title_info$title,
                unit = unit_name,
                panel_rows = style_config$panel_rows,
                panel_cols = style_config$panel_cols,
                panel_var = panel_var,
                show_total = show_total,
                invert_axis = invert_axis,
                top_impact = top_impact,
                plot_style_config = style_config
              )

              plot_list[[title_info$export_name]] <- p
            }
          } else {
            title_info <- .handle_plot_title_and_export(
              var_name = NULL,
              sep_value = NULL,
              plot_type = "stack",
              is_macro_mode = TRUE,
              variable_col = variable_col,
              unit_name = unit_name,
              style_config = style_config,
              data = unit_data
            )

            p <- .create_single_stacked_plot(
              data = unit_data,
              total_data = total_data,
              x_axis_from = x_axis_from,
              stack_value_from = stack_value_from,
              plot_title = title_info$title,
              unit = unit_name,
              panel_rows = style_config$panel_rows,
              panel_cols = style_config$panel_cols,
              panel_var = panel_var,
              show_total = show_total,
              invert_axis = invert_axis,
              top_impact = top_impact,
              plot_style_config = style_config
            )

            plot_list[[title_info$export_name]] <- p
          }
        }
      } else {
        # For comparison plots in macro mode
        if (separate_figure) {
          panel_values <- unique(unit_data[[panel_var]])

          for (panel_val in panel_values) {
            panel_data <- unit_data[unit_data[[panel_var]] == panel_val, ]

            title_info <- .handle_plot_title_and_export(
              var_name = "Global Economic Impacts",
              sep_value = panel_val,
              plot_type = "comparison",
              is_macro_mode = TRUE,
              variable_col = variable_col,
              unit_name = unit_name,
              style_config = style_config,
              data = panel_data,
              separate_figure = TRUE,
              panel_val = panel_val
            )

            p <- .create_single_comparison_plot(
              data = panel_data,
              x_axis_from = x_axis_from,
              plot_title = title_info$title,
              unit = unit_name,
              panel_rows = style_config$panel_rows,
              panel_cols = style_config$panel_cols,
              panel_var = panel_var,
              invert_axis = invert_axis,
              plot_style_config = style_config
            )

            plot_list[[title_info$export_name]] <- p
          }
        } else {
          title_info <- .handle_plot_title_and_export(
            var_name = "Global Economic Impacts",
            plot_type = "comparison",
            is_macro_mode = TRUE,
            unit_name = unit_name,
            style_config = style_config,
            data = unit_data
          )

          p <- .create_single_comparison_plot(
            data = unit_data,
            x_axis_from = x_axis_from,
            plot_title = title_info$title,
            unit = unit_name,
            panel_rows = style_config$panel_rows,
            panel_cols = style_config$panel_cols,
            panel_var = panel_var,
            invert_axis = invert_axis,
            plot_style_config = style_config
          )

          plot_list[[title_info$export_name]] <- p
        }
      }
    } else {
      # Not in macro mode - process data by split values
      # Get split column name
      split_col <- if (length(split_by) > 1) "split_display" else split_by

      # Get unique values for splitting
      separate_values_names <- unique(unit_data[[split_col]])

      # Process each separate value
      for (sep_value in separate_values_names) {
        filtered_data <- unit_data[unit_data[[split_col]] == sep_value, ]

        # For stack plots, calculate totals
        if (plot_type == "stack") {
          total_data <- .calculate_stack_totals(filtered_data, x_axis_from, panel_var)

          if (unstack_plot) {
            # Handle unstacked plots
            x_axis_values <- unique(filtered_data[[x_axis_from]])

            for (x_val in x_axis_values) {
              x_data <- filtered_data[filtered_data[[x_axis_from]] == x_val, ]
              x_totals <- total_data[total_data[[x_axis_from]] == x_val, ]

              if (separate_figure) {
                panel_values <- unique(x_data[[panel_var]])

                for (panel_val in panel_values) {
                  panel_x_data <- x_data[x_data[[panel_var]] == panel_val, ]
                  panel_x_totals <- x_totals[x_totals[[panel_var]] == panel_val, ]

                  title_info <- .handle_plot_title_and_export(
                    var_name = NULL,
                    sep_value = sep_value,
                    x_value = x_val,
                    plot_type = "unstack",
                    is_macro_mode = FALSE,
                    split_by = split_by,
                    x_axis_from = x_axis_from,
                    variable_col = variable_col,
                    unit_name = unit_name,
                    style_config = style_config,
                    data = panel_x_data,
                    separate_figure = TRUE,
                    panel_val = panel_val
                  )

                  p <- .create_single_unstacked_plot(
                    data = panel_x_data,
                    total_data = panel_x_totals,
                    x_axis_from = x_axis_from,
                    stack_value_from = stack_value_from,
                    plot_title = title_info$title,
                    unit = unit_name,
                    panel_rows = style_config$panel_rows,
                    panel_cols = style_config$panel_cols,
                    panel_var = panel_var,
                    invert_axis = invert_axis,
                    top_impact = top_impact,
                    plot_style_config = style_config
                  )

                  plot_list[[title_info$export_name]] <- p
                }
              } else {
                title_info <- .handle_plot_title_and_export(
                  var_name = NULL,
                  sep_value = sep_value,
                  x_value = x_val,
                  plot_type = "unstack",
                  is_macro_mode = FALSE,
                  split_by = split_by,
                  x_axis_from = x_axis_from,
                  variable_col = variable_col,
                  unit_name = unit_name,
                  style_config = style_config,
                  data = x_data
                )

                p <- .create_single_unstacked_plot(
                  data = x_data,
                  total_data = x_totals,
                  x_axis_from = x_axis_from,
                  stack_value_from = stack_value_from,
                  plot_title = title_info$title,
                  unit = unit_name,
                  panel_rows = style_config$panel_rows,
                  panel_cols = style_config$panel_cols,
                  panel_var = panel_var,
                  invert_axis = invert_axis,
                  top_impact = top_impact,
                  plot_style_config = style_config
                )

                plot_list[[title_info$export_name]] <- p
              }
            }
          } else {
            # Regular stacked plots
            if (separate_figure) {
              panel_values <- unique(filtered_data[[panel_var]])

              for (panel_val in panel_values) {
                panel_data <- filtered_data[filtered_data[[panel_var]] == panel_val, ]
                panel_totals <- total_data[total_data[[panel_var]] == panel_val, ]

                title_info <- .handle_plot_title_and_export(
                  var_name = NULL,
                  sep_value = sep_value,
                  plot_type = "stack",
                  is_macro_mode = FALSE,
                  split_by = split_by,
                  x_axis_from = x_axis_from,
                  variable_col = variable_col,
                  unit_name = unit_name,
                  style_config = style_config,
                  data = panel_data,
                  separate_figure = TRUE,
                  panel_val = panel_val
                )

                p <- .create_single_stacked_plot(
                  data = panel_data,
                  total_data = panel_totals,
                  x_axis_from = x_axis_from,
                  stack_value_from = stack_value_from,
                  plot_title = title_info$title,
                  unit = unit_name,
                  panel_rows = style_config$panel_rows,
                  panel_cols = style_config$panel_cols,
                  panel_var = panel_var,
                  show_total = show_total,
                  invert_axis = invert_axis,
                  top_impact = top_impact,
                  plot_style_config = style_config
                )

                plot_list[[title_info$export_name]] <- p
              }
            } else {
              title_info <- .handle_plot_title_and_export(
                var_name = NULL,
                sep_value = sep_value,
                plot_type = "stack",
                is_macro_mode = FALSE,
                split_by = split_by,
                x_axis_from = x_axis_from,
                variable_col = variable_col,
                unit_name = unit_name,
                style_config = style_config,
                data = filtered_data
              )

              p <- .create_single_stacked_plot(
                data = filtered_data,
                total_data = total_data,
                x_axis_from = x_axis_from,
                stack_value_from = stack_value_from,
                plot_title = title_info$title,
                unit = unit_name,
                panel_rows = style_config$panel_rows,
                panel_cols = style_config$panel_cols,
                panel_var = panel_var,
                show_total = show_total,
                invert_axis = invert_axis,
                top_impact = top_impact,
                plot_style_config = style_config
              )

              plot_list[[title_info$export_name]] <- p
            }
          }
        } else if (plot_type == "detail") {
          # Handle detail plots
          var_combinations <- unique(filtered_data[[variable_col]])

          for (var_name in var_combinations) {
            var_data <- filtered_data[filtered_data[[variable_col]] == var_name, ]

            if (separate_figure) {
              panel_values <- unique(var_data[[panel_var]])

              for (panel_val in panel_values) {
                panel_data <- var_data[var_data[[panel_var]] == panel_val, ]

                title_info <- .handle_plot_title_and_export(
                  var_name = var_name,
                  sep_value = sep_value,
                  plot_type = "detail",
                  is_macro_mode = FALSE,
                  split_by = split_by,
                  x_axis_from = x_axis_from,
                  variable_col = variable_col,
                  unit_name = unit_name,
                  style_config = style_config,
                  data = panel_data,
                  separate_figure = TRUE,
                  panel_val = panel_val
                )

                p <- .create_single_detail_plot(
                  data = panel_data,
                  x_axis_from = x_axis_from,
                  plot_title = title_info$title,
                  unit = unit_name,
                  panel_rows = style_config$panel_rows,
                  panel_cols = style_config$panel_cols,
                  panel_var = panel_var,
                  invert_axis = invert_axis,
                  top_impact = top_impact,
                  plot_style_config = style_config
                )

                plot_list[[title_info$export_name]] <- p
              }
            } else {
              title_info <- .handle_plot_title_and_export(
                var_name = var_name,
                sep_value = sep_value,
                plot_type = "detail",
                is_macro_mode = FALSE,
                split_by = split_by,
                x_axis_from = x_axis_from,
                variable_col = variable_col,
                unit_name = unit_name,
                style_config = style_config,
                data = var_data
              )

              p <- .create_single_detail_plot(
                data = var_data,
                x_axis_from = x_axis_from,
                plot_title = title_info$title,
                unit = unit_name,
                panel_rows = style_config$panel_rows,
                panel_cols = style_config$panel_cols,
                panel_var = panel_var,
                invert_axis = invert_axis,
                top_impact = top_impact,
                plot_style_config = style_config
              )

              plot_list[[title_info$export_name]] <- p
            }
          }
        } else {
          # Comparison plots
          if (separate_figure) {
            panel_values <- unique(filtered_data[[panel_var]])

            for (panel_val in panel_values) {
              panel_data <- filtered_data[filtered_data[[panel_var]] == panel_val, ]

              title_info <- .handle_plot_title_and_export(
                var_name = NULL,
                sep_value = sep_value,
                plot_type = "comparison",
                is_macro_mode = FALSE,
                split_by = split_by,
                x_axis_from = x_axis_from,
                variable_col = variable_col,
                unit_name = unit_name,
                style_config = style_config,
                data = panel_data,
                separate_figure = TRUE,
                panel_val = panel_val
              )

              p <- .create_single_comparison_plot(
                data = panel_data,
                x_axis_from = x_axis_from,
                plot_title = title_info$title,
                unit = unit_name,
                panel_rows = style_config$panel_rows,
                panel_cols = style_config$panel_cols,
                panel_var = panel_var,
                invert_axis = invert_axis,
                plot_style_config = style_config
              )

              plot_list[[title_info$export_name]] <- p
            }
          } else {
            title_info <- .handle_plot_title_and_export(
              var_name = NULL,
              sep_value = sep_value,
              plot_type = "comparison",
              is_macro_mode = FALSE,
              split_by = split_by,
              x_axis_from = x_axis_from,
              variable_col = variable_col,
              unit_name = unit_name,
              style_config = style_config,
              data = filtered_data
            )

            p <- .create_single_comparison_plot(
              data = filtered_data,
              x_axis_from = x_axis_from,
              plot_title = title_info$title,
              unit = unit_name,
              panel_rows = style_config$panel_rows,
              panel_cols = style_config$panel_cols,
              panel_var = panel_var,
              invert_axis = invert_axis,
              plot_style_config = style_config
            )

            plot_list[[title_info$export_name]] <- p
          }
        }
      }
    }
  }

  return(plot_list)
}

#' @title Create Plots with Panel Separation
#' @description
#' Handles the logic for creating single or multiple plots depending on `separate_figure`.
#' Dispatches to specific plot creators based on plot type.
#'
#' @param data A data frame for the plot.
#' @param total_data Optional totals (for stack/unstack).
#' @param unit_name Character. Unit label.
#' @param x_axis_from Character. X-axis column.
#' @param stack_value_from Character or NULL. Stack component column.
#' @param panel_var Character. Facet column.
#' @param separate_figure Logical. Whether to separate plots by panel.
#' @param style_config List. Style configuration.
#' @param plot_type Character. Plot type ("comparison", "detail", "stack", "unstack").
#' @param is_macro_mode Logical. Whether macro plot mode is on.
#' @param invert_axis Logical. Whether to flip bar orientation.
#' @param var_name Character or NULL. Variable name used in title.
#' @param sep_value Character or NULL. Split value.
#' @param split_by Character or NULL. Columns used to split plots.
#' @param variable_col Character or NULL. Column for variable labels.
#' @param top_impact Numeric or NULL. Top-N filtering.
#' @param show_total Logical. Whether to show totals in stacked bars.
#' @param x_val Character or NULL. Value of x-axis when in unstack mode.
#'
#' @return A list of ggplot2 objects keyed by export title.
#' @keywords internal
#' @noRd
.create_plots_with_panels <- function(data, total_data, unit_name, x_axis_from, stack_value_from,
                                      panel_var, separate_figure, style_config, plot_type,
                                      is_macro_mode, invert_axis, var_name = NULL, sep_value = NULL,
                                      split_by = NULL, variable_col = NULL, top_impact = NULL,
                                      show_total = FALSE, x_val = NULL) {
  plot_list <- list()

  # Get appropriate plot function
  plot_function <- switch(plot_type,
                          "comparison" = .create_single_comparison_plot,
                          "detail" = .create_single_detail_plot,
                          "stack" = .create_single_stacked_plot,
                          "unstack" = .create_single_unstacked_plot
  )

  if (separate_figure) {
    panel_values <- unique(data[[panel_var]])

    for (panel_val in panel_values) {
      panel_data <- data[data[[panel_var]] == panel_val, ]

      # Handle total data for stack/unstack plots
      panel_total_data <- if (!is.null(total_data)) {
        total_data[total_data[[panel_var]] == panel_val, ]
      } else {
        NULL
      }

      # Get title info
      title_info <- .handle_plot_title_and_export(
        var_name = var_name,
        sep_value = sep_value,
        x_value = x_val,
        plot_type = plot_type,
        is_macro_mode = is_macro_mode,
        split_by = split_by,
        x_axis_from = x_axis_from,
        variable_col = variable_col,
        unit_name = unit_name,
        style_config = style_config,
        data = panel_data,
        separate_figure = separate_figure,
        panel_val = panel_val
      )

      # Create plot
      if (plot_type %in% c("stack", "unstack")) {
        p <- plot_function(
          data = panel_data,
          total_data = panel_total_data,
          x_axis_from = x_axis_from,
          stack_value_from = stack_value_from,
          plot_title = title_info$title,
          unit = unit_name,
          panel_rows = style_config$panel_rows,
          panel_cols = style_config$panel_cols,
          panel_var = panel_var,
          show_total = show_total,
          invert_axis = invert_axis,
          top_impact = top_impact,
          plot_style_config = style_config
        )
      } else if (plot_type == "detail") {
        p <- plot_function(
          data = panel_data,
          x_axis_from = x_axis_from,
          plot_title = title_info$title,
          unit = unit_name,
          panel_rows = style_config$panel_rows,
          panel_cols = style_config$panel_cols,
          panel_var = panel_var,
          invert_axis = invert_axis,
          top_impact = top_impact,
          plot_style_config = style_config
        )
      } else if (plot_type == "comparison") {
        p <- plot_function(
          data = panel_data,
          x_axis_from = x_axis_from,
          plot_title = title_info$title,
          unit = unit_name,
          panel_rows = style_config$panel_rows,
          panel_cols = style_config$panel_cols,
          panel_var = panel_var,
          invert_axis = invert_axis,
          plot_style_config = style_config
        )
      }

      plot_list[[title_info$export_name]] <- p
    }
  } else {
    # Get title info
    title_info <- .handle_plot_title_and_export(
      var_name = var_name,
      sep_value = sep_value,
      x_value = x_val,
      plot_type = plot_type,
      is_macro_mode = is_macro_mode,
      split_by = split_by,
      x_axis_from = x_axis_from,
      variable_col = variable_col,
      unit_name = unit_name,
      style_config = style_config,
      data = data
    )

    # Create plot
    if (plot_type %in% c("stack", "unstack")) {
      p <- plot_function(
        data = data,
        total_data = total_data,
        x_axis_from = x_axis_from,
        stack_value_from = stack_value_from,
        plot_title = title_info$title,
        unit = unit_name,
        panel_rows = style_config$panel_rows,
        panel_cols = style_config$panel_cols,
        panel_var = panel_var,
        show_total = show_total,
        invert_axis = invert_axis,
        top_impact = top_impact,
        plot_style_config = style_config
      )
    } else if (plot_type == "detail") {
      p <- plot_function(
        data = data,
        x_axis_from = x_axis_from,
        plot_title = title_info$title,
        unit = unit_name,
        panel_rows = style_config$panel_rows,
        panel_cols = style_config$panel_cols,
        panel_var = panel_var,
        invert_axis = invert_axis,
        top_impact = top_impact,
        plot_style_config = style_config
      )
    } else if (plot_type == "comparison") {
      p <- plot_function(
        data = data,
        x_axis_from = x_axis_from,
        plot_title = title_info$title,
        unit = unit_name,
        panel_rows = style_config$panel_rows,
        panel_cols = style_config$panel_cols,
        panel_var = panel_var,
        invert_axis = invert_axis,
        plot_style_config = style_config
      )
    }

    plot_list[[title_info$export_name]] <- p
  }

  return(plot_list)
}

#' @title Finalize Plot Export Process
#' @description
#' Prepares and calls the unified export function for GTAPViz plots.
#' Adds defaults for filename and merges user-specified configurations.
#'
#' @param plot_list List of ggplot2 objects to export.
#' @param data Original data used for plotting.
#' @param panel_layout Panel layout specification (rows/cols).
#' @param output_path Character. Path to output directory.
#' @param export_picture Logical. Whether to export images.
#' @param export_as_pdf Logical or "merged". Whether to export as PDF.
#' @param export_config List. Export parameters (width, height, dpi, etc).
#' @param default_filename Character. Default export name if not specified.
#'
#' @return Invisibly returns the list of plots.
#' @keywords internal
#' @noRd
.finalize_plot_export <- function(plot_list, data, panel_layout, output_path, export_picture,
                                  export_as_pdf, export_config, default_filename) {

  # Only create export_config if it's NULL
  if (is.null(export_config)) {
    export_config <- list()
  }

  # Calculate dimensions if not already specified
  dimensions <- if (!is.null(export_config$width) && !is.null(export_config$height)) {
    list(width = export_config$width, height = export_config$height)
  } else {
    .calculate_plot_dimensions(data, panel_layout)
  }

  # Add calculated dimensions to export_config
  export_config$width <- dimensions$width
  export_config$height <- dimensions$height

  # Handle NULL output_path - use tempdir() if needed
  if (is.null(output_path) && (export_picture || export_as_pdf)) {
    output_path <- tempdir()
    message("No output path specified. Using temporary directory: ", output_path)
  }

  # Export plots
  if (export_picture || export_as_pdf) {
    .export_plot_output(
      plots = plot_list,
      output_path = output_path,
      export_picture = export_picture,
      export_as_pdf = export_as_pdf,
      export_config = export_config,
      data = data,
      panel_layout = panel_layout,
      default_filename = default_filename
    )
  }

  # Return plots
  return(invisible(plot_list))
}

# UNIFIED STYLING AND AXIS HELPERS ---------------------------------

#' @title Apply Axis Scale Configuration
#' @description
#' Applies a `scale_x_continuous` or `scale_y_continuous` scale to a ggplot axis
#' using the plot style configuration. Supports expansion and tick mark increment.
#'
#' @param limits Numeric vector of length 2 specifying axis limits.
#' @param style_config List. Style configuration containing expansion and increments.
#' @param axis Character. Either `"x"` or `"y"` (default `"y"`).
#'
#' @return A ggplot2 scale object.
#' @keywords internal
#' @noRd
.apply_axis_scale <- function(limits, style_config, axis = "y") {
  scale_args <- list(
    limits = limits,
    oob = scales::oob_keep,
    expand = ggplot2::expansion(mult = if (axis == "y") style_config$expansion_y_mult else style_config$expansion_x_mult)
  )

  # Add breaks if scale_increment is specified
  if (!is.null(style_config$scale_increment) && is.numeric(style_config$scale_increment)) {
    scale_args$breaks <- seq(limits[1], limits[2], by = style_config$scale_increment)
  }

  # Apply appropriate scale function
  if (axis == "y") {
    return(do.call(ggplot2::scale_y_continuous, scale_args))
  } else {
    return(do.call(ggplot2::scale_x_continuous, scale_args))
  }
}

#' @title Create Facet Wrap for Panels
#' @description
#' Generates a `facet_wrap` layout using consistent configuration based on the style config.
#'
#' @param panel_var Character. Column name used for faceting.
#' @param panel_rows Integer. Number of rows in the panel layout.
#' @param panel_cols Integer. Number of columns in the panel layout.
#' @param style_config List. Style configuration with axis facet controls.
#' @param free_scales Logical. Whether to use `"free"` scales (default: FALSE).
#'
#' @return A ggplot2 `facet_wrap` object.
#' @keywords internal
#' @noRd
.create_facet_wrap <- function(panel_var, panel_rows = NULL, panel_cols = NULL,
                               style_config = NULL, free_scales = FALSE) {
  facet_args <- list(
    as.formula(paste("~", panel_var)),
    scales = if (free_scales || (!is.null(style_config) && style_config$show_axis_titles_on_all_facets))
      "free" else "fixed"
  )

  if (!is.null(panel_rows)) {
    facet_args$nrow <- panel_rows
  }

  if (!is.null(panel_cols)) {
    facet_args$ncol <- panel_cols
  }

  return(do.call(ggplot2::facet_wrap, facet_args))
}

#' @title Add Zero Reference Line to Plot
#' @description
#' Adds a horizontal or vertical line at the zero position to a plot based on the configuration.
#'
#' @param plot A ggplot2 object.
#' @param invert_axis Logical. Whether the axis is inverted (horizontal bars).
#' @param style_config List. Contains zero line settings (position, color, type).
#'
#' @return A ggplot2 object with the zero line added.
#' @keywords internal
#' @noRd
.add_zero_line <- function(plot, invert_axis, style_config) {
  if (invert_axis) {
    return(plot + ggplot2::geom_vline(
      xintercept = style_config$zero_line_position,
      linetype = style_config$zero_line_type,
      color = style_config$zero_line_color,
      linewidth = style_config$zero_line_size
    ))
  } else {
    return(plot + ggplot2::geom_hline(
      yintercept = style_config$zero_line_position,
      linetype = style_config$zero_line_type,
      color = style_config$zero_line_color,
      linewidth = style_config$zero_line_size
    ))
  }
}

#' @title Format Axis Labels from Unit and Style
#' @description
#' Generates x- and y-axis labels based on unit, axis source, and style config.
#'
#' @param unit Character. Unit name.
#' @param x_axis_from Character. Column name for x-axis.
#' @param style_config List. Contains label descriptions or overrides.
#'
#' @return A list with `y_label` and `x_label` strings.
#' @keywords internal
#' @noRd
.format_axis_labels <- function(unit, x_axis_from, style_config) {
  # y-axis label shows the unit
  y_label <- if (!is.null(style_config$y_axis_description) && nzchar(style_config$y_axis_description)) {
    style_config$y_axis_description
  } else if (tolower(unit) == "percent") {
    "Percentage (%)"
  } else {
    unit
  }

  # x-axis label uses column name if no description provided
  x_label <- if (!is.null(style_config$x_axis_description) && nzchar(style_config$x_axis_description)) {
    style_config$x_axis_description
  } else {
    x_axis_from
  }

  return(list(y_label = y_label, x_label = x_label))
}

#' @title Format Y-Axis Label from Unit
#' @description
#' Simplified helper to return the correct y-axis label given the unit and style config.
#'
#' @param unit_name Character. Unit string.
#' @param style_config List. Style config possibly containing `y_axis_description`.
#'
#' @return A string for y-axis label.
#' @keywords internal
#' @noRd
.format_y_axis_label <- function(unit_name, style_config) {
  if (!is.null(style_config$y_axis_description) && nzchar(style_config$y_axis_description)) {
    return(style_config$y_axis_description)
  } else if (tolower(unit_name) == "percent") {
    return("Percentage (%)")
  } else {
    return(unit_name)
  }
}

#' @title Set Axis Labels on ggplot
#' @description
#' Applies plot title, x-label, and y-label depending on orientation and visibility flags.
#'
#' @param plot A ggplot2 object.
#' @param invert_axis Logical. Whether to flip orientation.
#' @param plot_title Character. Main plot title.
#' @param x_axis_label Character. Label for x-axis.
#' @param y_axis_label Character. Label for y-axis.
#' @param style_config List. Style config containing label visibility options.
#'
#' @return A ggplot2 object with updated axis labels.
#' @keywords internal
#' @noRd
.set_axis_labels <- function(plot, invert_axis, plot_title, x_axis_label, y_axis_label, style_config) {
  if (invert_axis) {
    # For horizontal bars, x is Value axis and y is Categories axis
    if (style_config$show_x_axis_title && style_config$show_y_axis_title) {
      # Both axis titles visible
      plot <- plot + ggplot2::labs(title = plot_title, x = y_axis_label, y = x_axis_label)
    } else if (style_config$show_y_axis_title) {
      # Only categories axis title visible
      plot <- plot + ggplot2::labs(title = plot_title, x = "", y = x_axis_label)
    } else if (style_config$show_x_axis_title) {
      # Only value axis title visible
      plot <- plot + ggplot2::labs(title = plot_title, x = y_axis_label, y = "")
    } else {
      # No axis titles
      plot <- plot + ggplot2::labs(title = plot_title, x = "", y = "")
    }
  } else {
    # For vertical bars, x is Categories axis and y is Value axis
    if (style_config$show_x_axis_title && style_config$show_y_axis_title) {
      # Both axis titles visible
      plot <- plot + ggplot2::labs(title = plot_title, y = y_axis_label, x = x_axis_label)
    } else if (style_config$show_x_axis_title) {
      # Only categories axis title visible
      plot <- plot + ggplot2::labs(title = plot_title, y = "", x = x_axis_label)
    } else if (style_config$show_y_axis_title) {
      # Only value axis title visible
      plot <- plot + ggplot2::labs(title = plot_title, y = y_axis_label, x = "")
    } else {
      # No axis titles
      plot <- plot + ggplot2::labs(title = plot_title, y = "", x = "")
    }
  }

  return(plot)
}

#' @title Calculate Y-Axis Limits
#' @description
#' Computes value axis limits using unit type and data characteristics, or returns
#' manual limits from the config.
#'
#' @param data A data frame containing a `Value` column.
#' @param unit Character. Unit label (e.g., `"percent"`).
#' @param style_config List. Contains optional `scale_limit` or guides padding behavior.
#'
#' @return A numeric vector of length 2 indicating y-axis limits.
#' @keywords internal
#' @noRd
.calculate_value_axis_limits <- function(data, unit, style_config) {
  # Use scale_limit if provided
  if (!is.null(style_config$scale_limit) && length(style_config$scale_limit) == 2) {
    return(style_config$scale_limit)
  }

  # Calculate appropriate limits based on data
  value_range <- range(data$Value, na.rm = TRUE)
  max_abs_value <- max(abs(value_range), na.rm = TRUE)

  # Different limit calculations based on data and unit
  if (tolower(unit) == "percent") {
    # For percentage values, use symmetric limits
    return(c(-max_abs_value * 1.35, max_abs_value * 1.35))
  } else {
    # For other units, base on value range
    if (all(data$Value >= 0, na.rm = TRUE)) {
      # All positive values
      return(c(0, value_range[2] * 1.3))
    } else if (all(data$Value <= 0, na.rm = TRUE)) {
      # All negative values
      return(c(value_range[1] * 1.3, 0))
    } else {
      # Mixed values
      return(c(value_range[1] * 1.3, value_range[2] * 1.3))
    }
  }
}


# COMMON DATA HANDLING HELPERS ---------------------------------

#' @title Find Column Case-Insensitive
#' @description
#' Searches for a column name in a data frame with optional case-insensitive matching and fallback options.
#'
#' @param data A data frame.
#' @param col_name Character. Desired column name.
#' @param is_required Logical. If TRUE, throw an error when not found.
#' @param default_name Character. Optional fallback if column not found.
#'
#' @return Matching column name or fallback.
#' @keywords internal
#' @noRd
.find_column <- function(data, col_name, is_required = FALSE, default_name = NULL) {
  # Find a column case-insensitively
  if (col_name %in% names(data)) {
    return(col_name)  # Exact match
  }

  idx <- which(tolower(names(data)) == tolower(col_name))
  if (length(idx) > 0) {
    return(names(data)[idx[1]])  # Case-insensitive match
  }

  if (is_required) {
    stop(paste("Required column not found:", col_name))
  }

  return(default_name)  # Return default or NULL
}

#' @title Process Filter Variable for GTAPViz Data
#'
#' @description
#' Applies filtering to a GTAP-compatible data frame based on a specified `filter_var`.
#' This function supports filtering using a vector, list of column-value pairs, or
#' a data frame of filter values matched by `variable_col`.
#' @md
#' @param data A data frame containing GTAP result variables.
#' @param filter_var A filter object that can be:
#'   - A vector of values to filter in the column specified by `variable_col`.
#'   - A list of column-value pairs for multi-column filtering.
#'   - A data frame with a `variable_col` column to match values in `data`.
#' @param variable_col Character. Column name to match values when `filter_var` is a vector or data frame.
#'
#' @return A filtered data frame with rows matching the specified criteria.
#' If no matches are found, a warning is issued and an empty data frame is returned.
#'
#' @details
#' This function is called internally during data preprocessing for GTAPViz plots.
#' If `filter_var` is NULL, the original `data` is returned unmodified.
#'
#' @author Pattawee Puangchit
#' @keywords internal
#' @noRd
.process_filter_var <- function(data, filter_var, variable_col) {
  # If filter_var is NULL, return data as is
  if (is.null(filter_var)) {
    return(data)
  }

  # Check if data is a data frame
  if (!is.data.frame(data)) {
    stop("Input data must be a data frame. Current data structure is ", class(data)[1], ".")
  }

  # Case 1: filter_var is a list of column/value pairs
  if (is.list(filter_var) && !is.data.frame(filter_var)) {
    filtered_data <- data

    for (col_name in names(filter_var)) {
      filter_values <- filter_var[[col_name]]

      # Check if column exists in data
      if (!col_name %in% names(filtered_data)) {
        warning(paste0("Filter column '", col_name, "' not found in data. Skipping this filter."))
        next
      }

      # Apply filter for this column
      filtered_data <- filtered_data[filtered_data[[col_name]] %in% filter_values, ]

      # Stop if no data left after filtering
      if (nrow(filtered_data) == 0) {
        warning(paste0("No data remains after filtering by column '", col_name,
                       "' with values: ", paste(filter_values, collapse=", "), "."))
        return(filtered_data)
      }
    }

    return(filtered_data)
  }

  # Case 2: filter_var is a data frame
  if (is.data.frame(filter_var)) {
    if (!variable_col %in% names(filter_var)) {
      stop("Variable column '", variable_col, "' not found in filter_var data frame.")
    }

    if (!variable_col %in% names(data)) {
      stop("Variable column '", variable_col, "' not found in input data.")
    }

    filtered_data <- data[data[[variable_col]] %in% filter_var[[variable_col]], ]

    if (nrow(filtered_data) == 0) {
      warning("No matching data found for the specified filter_var values.")
    }

    return(filtered_data)
  }

  # Case 3: filter_var is a vector (original behavior)
  if (is.vector(filter_var) && !is.list(filter_var)) {
    if (!variable_col %in% names(data)) {
      stop("Variable column '", variable_col, "' not found in input data.")
    }

    filtered_data <- data[data[[variable_col]] %in% filter_var, ]

    if (nrow(filtered_data) == 0) {
      warning(paste0("No matching data found for filter values: ", paste(filter_var, collapse=", ")))
    }

    return(filtered_data)
  }

  # If filter_var is of an unexpected type
  stop("filter_var must be a vector, list, or data frame. Current type is ", class(filter_var)[1], ".")
}

#' @title Prepare Data Source for Plotting
#' @description
#' Validates and retrieves a data frame containing required columns from either a data frame or list of frames.
#'
#' @param data A data frame or list of data frames.
#' @param x_axis_from Character. Required column for x-axis.
#' @param stack_value_from Character. Optional column for stacked bars.
#' @param variable_col Character. Optional column for variable codes.
#'
#' @return A validated data frame.
#' @keywords internal
#' @noRd
.prepare_data_source <- function(data, x_axis_from, stack_value_from = NULL, variable_col = NULL) {
  # Check if data is completely missing
  if (is.null(data)) {
    stop("Input data is NULL. Please provide a valid data frame or list of data frames.")
  }

  # Check data type and provide helpful message
  if (!is.data.frame(data) && !is.list(data)) {
    stop(paste0("Invalid data format. Expected a data frame or list of data frames, but got: ",
                class(data)[1], ". Please check your input data."))
  }

  # If already a data frame, validate columns
  if (is.data.frame(data)) {
    # Check x_axis_from column
    if (!(x_axis_from %in% names(data))) {
      stop(paste0("Required column '", x_axis_from, "' not found in the data frame. ",
                  "Available columns are: ", paste(names(data), collapse=", ")))
    }

    # Check stack_value_from if provided (for stack_plot)
    if (!is.null(stack_value_from) && !(stack_value_from %in% names(data))) {
      stop(paste0("Required column '", stack_value_from, "' not found in the data frame. ",
                  "Available columns are: ", paste(names(data), collapse=", ")))
    }

    # Check variable_col if provided
    if (!is.null(variable_col) && !(variable_col %in% names(data))) {
      stop(paste0("Required column '", variable_col, "' not found in the data frame. ",
                  "Available columns are: ", paste(names(data), collapse=", ")))
    }

    return(data)
  }

  # If a list of data frames, find first matching data frame
  if (is.list(data)) {
    if (length(data) == 0) {
      stop("Empty list provided. The list must contain at least one data frame.")
    }

    valid_dfs <- 0
    for (df_name in names(data)) {
      df <- data[[df_name]]
      if (is.data.frame(df)) {
        valid_dfs <- valid_dfs + 1
        # Check x_axis_from column
        if (x_axis_from %in% names(df)) {
          # Check stack_value_from if provided
          if (!is.null(stack_value_from) && !(stack_value_from %in% names(df))) {
            next
          }

          # Check variable_col if provided
          if (!is.null(variable_col) && !(variable_col %in% names(df))) {
            next
          }

          return(df)
        }
      }
    }

    # Helpful error messages based on what was found
    if (valid_dfs == 0) {
      stop("No valid data frames found in the input list. Please check your data structure.")
    } else {
      stop(paste0("No suitable data frame found with required column '", x_axis_from, "'. ",
                  "Checked ", valid_dfs, " data frames but none contained all required columns."))
    }
  }

  # This should never be reached due to the initial type check, but including for completeness
  stop("Input must be a data frame or a list of data frames.")
}

#' @title Check and Standardize Unit Column
#' @description
#' Validates and resolves the unit column in a data frame.
#'
#' @param data A data frame.
#' @param unit_col Character. Column name to use as unit (default: "Unit").
#'
#' @return A list containing the possibly modified data and resolved `unit_col`.
#' @keywords internal
#' @noRd
.check_unit_column <- function(data, unit_col = "Unit") {
  # Check if unit column exists
  actual_unit_col <- .find_column(data, unit_col)

  if (is.null(actual_unit_col)) {
    warning(paste("Unit column", unit_col, "not found. Using default 'Unit'"))
    data$Unit <- "data"
    return(list(data = data, unit_col = "Unit"))
  }

  return(list(data = data, unit_col = actual_unit_col))
}

#' @title Format Variable Names Using Descriptions
#' @description
#' Formats `Variable` column using corresponding `Description` values, with optional concatenation.
#'
#' @param data A data frame.
#' @param variable_col Character. Variable name column.
#' @param desc_col Character. Description column.
#' @param var_name_by_description Logical. Use description as primary label.
#' @param add_var_info Logical. Append variable code to description.
#'
#' @return Modified data frame with formatted variable labels.
#' @keywords internal
#' @noRd
.format_variable_names <- function(data, variable_col, desc_col, var_name_by_description = TRUE,
                                   add_var_info = FALSE) {
  if (!is.data.frame(data) || !variable_col %in% names(data))
    return(data)

  # If no Description column, return data unchanged
  if (!desc_col %in% names(data))
    return(data)

  result <- data

  for (i in seq_len(nrow(result))) {
    var_ <- result[[variable_col]][i]
    des_ <- result[[desc_col]][i]

    # Handle missing or empty description
    if (is.na(des_) || !nzchar(des_))
      des_ <- var_

    if (var_name_by_description && add_var_info) {
      # Both: Description (Variable)
      result[[variable_col]][i] <- paste0(des_, " (", var_, ")")
    } else if (var_name_by_description && !add_var_info) {
      # Description only
      result[[variable_col]][i] <- des_
    } else if (!var_name_by_description && add_var_info) {
      # Variable (Description), but only if different
      if (des_ != var_) {
        result[[variable_col]][i] <- paste0(var_, " (", des_, ")")
      }
    } else {
      # Variable only (default, do nothing)
    }
  }

  return(result)
}

#' @title Calculate Panel Layout Automatically
#' @description
#' Calculates rows and columns for facet panels based on user-specified or auto-detected layout.
#'
#' @param data A data frame.
#' @param panel_rows Integer. Desired number of rows.
#' @param panel_cols Integer. Desired number of columns.
#' @param panel_var Character. Facet column (default: "Experiment").
#'
#' @return A list with `rows` and `cols` for panel layout.
#' @keywords internal
#' @noRd
.calculate_panel_layout <- function(data, panel_rows = NULL, panel_cols = NULL, panel_var = "Experiment") {
  # Determine number of panels
  num_panels <- if (is.data.frame(data) && panel_var %in% names(data)) {
    length(unique(data[[panel_var]]))
  } else {
    1
  }

  # Case 1: Only panel_rows is specified (panel_cols is NULL)
  if (!is.null(panel_rows) && is.null(panel_cols)) {
    panel_cols <- ceiling(num_panels / panel_rows)
    return(list(rows = panel_rows, cols = panel_cols))
  }
  # Case 2: Only panel_cols is specified (panel_rows is NULL)
  else if (is.null(panel_rows) && !is.null(panel_cols)) {
    panel_rows <- ceiling(num_panels / panel_cols)
    return(list(rows = panel_rows, cols = panel_cols))
  }
  # Case 3: Both panel_rows and panel_cols are specified (neither is NULL)
  else if (!is.null(panel_rows) && !is.null(panel_cols)) {
    # Check if there are enough panels and adjust if needed
    if (panel_rows * panel_cols < num_panels) {
      warning("Provided dimensions insufficient. Adjusting columns to fit all panels.")
      # Preferentially adjust columns to fit all panels
      panel_cols <- ceiling(num_panels / panel_rows)
    }
    return(list(rows = panel_rows, cols = panel_cols))
  }

  # Case 4: AUTO CALCULATE LAYOUT WHEN NEITHER DIMENSION IS SPECIFIED
  if (num_panels <= 1) {
    return(list(rows = 1, cols = 1))
  } else if (num_panels <= 3) {
    return(list(rows = 1, cols = num_panels))
  } else if (num_panels <= 4) {
    return(list(rows = 2, cols = 2))
  } else if (num_panels <= 6) {
    return(list(rows = 2, cols = 3))
  } else if (num_panels <= 9) {
    return(list(rows = 3, cols = 3))
  } else if (num_panels <= 12) {
    return(list(rows = 3, cols = 4))
  } else {
    # For larger numbers, try to find a balanced layout
    factors <- c()
    for (i in 1:sqrt(num_panels)) {
      if (num_panels %% i == 0) {
        factors <- c(factors, i)
      }
    }

    if (length(factors) > 0) {
      best_factor <- factors[length(factors)]
      rows <- best_factor
      cols <- num_panels / best_factor
    } else {
      # If not divisible evenly, use a layout that can fit all panels
      cols <- ceiling(sqrt(num_panels))
      rows <- ceiling(num_panels / cols)
    }

    # Ensure layout is not too wide compared to height
    if (cols > 2 * rows) {
      new_cols <- ceiling(sqrt(num_panels))
      new_rows <- ceiling(num_panels / new_rows)
      rows <- new_rows
      cols <- new_cols
    }

    return(list(rows = rows, cols = cols))
  }
}

#' @title Calculate Plot Dimensions for Export
#' @description
#' Determines width and height based on number of panels to ensure readable layout.
#'
#' @param data A data frame.
#' @param panel_layout A list with `rows` and `cols` values.
#'
#' @return A list with `width` and `height` in inches.
#' @keywords internal
#' @noRd
.calculate_plot_dimensions <- function(data, panel_layout) {
  num_panels <- panel_layout$rows * panel_layout$cols
  base_width <- 20
  base_height <- 12

  width <- if(num_panels <= 4) {
    base_width
  } else {
    min(base_width + (num_panels - 4) * 3.5, 50)
  }

  height <- base_height * 0.75

  return(list(width = width, height = height))
}

#' @title Filter Top Impact Values (Detail Plot)
#' @description
#' Filters the most impactful positive and negative values within each group for detailed plots.
#'
#' @param data A data frame or list.
#' @param top_impact Integer. Number of top observations to retain per group.
#' @param group_col Character. Grouping variable.
#' @param panel_var Character. Facet variable.
#' @param x_axis_from Character. X-axis variable.
#' @param variable_col Character. Variable identifier.
#' @param unit_col Character. Unit column.
#'
#' @return Filtered data frame or list.
#' @keywords internal
#' @noRd
.filter_top_impact_values_detail <- function(data, top_impact, group_col, panel_var,
                                             x_axis_from, variable_col, unit_col) {
  if (inherits(data, "list") && !is.data.frame(data)) {
    return(.apply_to_dataframes(data, .filter_top_impact_values_detail, top_impact,
                                group_col, panel_var, x_axis_from, variable_col, unit_col))
  }

  if (!is.data.frame(data)) return(data)
  if (!("Value" %in% names(data))) return(data)
  if (is.null(top_impact) || nrow(data) <= top_impact) return(data)

  data$Value <- as.numeric(data$Value)

  # Create grouping based on variable, unit, panel and group columns
  group_cols <- c(variable_col, unit_col, panel_var, group_col)
  group_cols <- group_cols[group_cols %in% names(data)]

  # Create a group identifier
  group_id_parts <- lapply(group_cols, function(col) data[[col]])
  group_id <- do.call(paste, c(group_id_parts, sep = "_"))
  data$._group_id_ <- group_id

  # Split data by groups
  data_grouped <- split(data, data$._group_id_)

  # Filter logic
  filtered_list <- lapply(data_grouped, function(df) {
    if (nrow(df) <= top_impact) return(df)

    df_pos <- df[df$Value > 0, , drop = FALSE]
    df_neg <- df[df$Value < 0, , drop = FALSE]

    pos_count <- min(nrow(df_pos), ceiling(top_impact / 2))
    neg_count <- min(nrow(df_neg), ceiling(top_impact / 2))

    if (neg_count < ceiling(top_impact / 2)) {
      pos_count <- min(nrow(df_pos), top_impact - neg_count)
    }

    if (pos_count < ceiling(top_impact / 2)) {
      neg_count <- min(nrow(df_neg), top_impact - pos_count)
    }

    rbind(
      if (pos_count > 0) df_pos[order(-df_pos$Value), , drop = FALSE][seq_len(pos_count), , drop = FALSE] else NULL,
      if (neg_count > 0) df_neg[order(df_neg$Value), , drop = FALSE][seq_len(neg_count), , drop = FALSE] else NULL
    )
  })

  filtered_data <- do.call(rbind, filtered_list)

  # Remove the temporary group_id column
  filtered_data$._group_id_ <- NULL

  # Calculate average values for sorting the axis variable
  if (x_axis_from %in% names(filtered_data)) {
    avg_formula <- as.formula(paste("Value ~", x_axis_from))
    avg_values <- stats::aggregate(avg_formula, data = filtered_data, mean, na.rm = TRUE)
    sorted_groups <- avg_values[order(avg_values$Value), 1]
    filtered_data[[x_axis_from]] <- factor(filtered_data[[x_axis_from]], levels = sorted_groups)
  }

  # Return filtered data
  return(filtered_data)
}

#' @title Calculate Stack Totals
#' @description
#' Aggregates total, positive, and negative values by `x_axis_from` and `panel_var`.
#'
#' @param data A data frame or list.
#' @param x_axis_from Character. X-axis variable.
#' @param panel_var Character. Facet variable.
#'
#' @return A data frame with totals and formatted labels.
#' @keywords internal
#' @noRd
.calculate_stack_totals <- function(data, x_axis_from, panel_var) {
  if (inherits(data, "list") && !is.data.frame(data)) {
    return(.apply_to_dataframes(data, .calculate_stack_totals, x_axis_from, panel_var))
  }

  # INPUT VALIDATION
  if (!is.data.frame(data)) return(NULL)
  if (!all(c(x_axis_from, panel_var, "Value") %in% names(data))) return(NULL)

  # Ensure Value column is numeric
  data$Value <- as.numeric(data$Value)

  # SETUP VARIABLES
  group_cols <- c(panel_var, x_axis_from)
  total_data <- data.frame()

  # GET UNIQUE VALUES
  unique_x_values <- unique(data[[x_axis_from]])
  unique_panel_values <- unique(data[[panel_var]])

  # CALCULATE TOTALS FOR EACH COMBINATION
  for (x_val in unique_x_values) {
    for (panel_val in unique_panel_values) {
      subset_data <- data[data[[x_axis_from]] == x_val & data[[panel_var]] == panel_val, ]

      if (nrow(subset_data) > 0) {
        positive_total <- sum(pmax(subset_data$Value, 0), na.rm = TRUE)
        negative_total <- sum(pmin(subset_data$Value, 0), na.rm = TRUE)
        total_value <- sum(subset_data$Value, na.rm = TRUE)

        # CREATE ROW WITH TOTALS
        row <- data.frame(
          panel_val = panel_val,
          x_val = x_val,
          Total = total_value,
          PositiveTotal = positive_total,
          NegativeTotal = negative_total,
          stringsAsFactors = FALSE
        )
        names(row)[1] <- panel_var
        names(row)[2] <- x_axis_from

        total_data <- rbind(total_data, row)
      }
    }
  }

  # FORMAT TOTAL LABELS
  total_data$TotalLabel <- sprintf("Total\n%.2f", total_data$Total)

  return(total_data)
}

#' @title Filter Top Impact Values (Stack Plot)
#' @description
#' Filters stack components to retain only top impactful bars based on total impact.
#'
#' @param data A data frame or list.
#' @param total_data Data frame of precomputed totals.
#' @param top_impact Integer. Number of top values to retain.
#' @param group_col Character. Grouping column.
#' @param panel_var Character. Facet variable.
#' @param x_axis_from Character. X-axis column.
#' @param variable_col Character. Variable code column.
#' @param unit_col Character. Unit column.
#' @param stack_value_from Character. Column representing stack components.
#'
#' @return Filtered data frame.
#' @keywords internal
#' @noRd
.filter_top_impact_values_stack <- function(data, total_data, top_impact, is_macro_mode, split_by,
                                            x_axis_from, panel_var, variable_col, unit_col) {
  if (inherits(data, "list") && !is.data.frame(data)) {
    return(.apply_to_dataframes(data, .filter_top_impact_values_stack, total_data, top_impact,
                                is_macro_mode, split_by, x_axis_from, panel_var, variable_col, unit_col))
  }

  # INPUT VALIDATION
  if (!is.data.frame(data)) return(data)
  if (!("Value" %in% names(data))) return(data)
  if (is.null(top_impact) || nrow(total_data) <= top_impact) return(data)

  # GET GROUP COLUMNS
  group_cols <- c(variable_col, unit_col, panel_var, split_by)
  group_cols <- group_cols[group_cols %in% names(total_data)]

  # CREATE GROUP IDENTIFIER
  group_id_parts <- lapply(group_cols, function(col) total_data[[col]])
  group_id <- do.call(paste, c(group_id_parts, sep = "_"))
  total_data$._group_id_ <- group_id

  # FILTER TOTALS BY IMPACT
  filtered_total_list <- list()
  for (group in unique(group_id)) {
    group_data <- total_data[total_data$._group_id_ == group, ]

    # SEPARATE POSITIVE AND NEGATIVE IMPACTS
    group_pos <- group_data[group_data$Total > 0, , drop = FALSE]
    group_neg <- group_data[group_data$Total < 0, , drop = FALSE]

    # CALCULATE NUMBER OF EACH TO INCLUDE
    pos_count <- min(nrow(group_pos), ceiling(top_impact / 2))
    neg_count <- min(nrow(group_neg), ceiling(top_impact / 2))

    # ADJUST COUNTS IF NEEDED
    if (neg_count < ceiling(top_impact / 2)) {
      pos_count <- min(nrow(group_pos), top_impact - neg_count)
    }
    if (pos_count < ceiling(top_impact / 2)) {
      neg_count <- min(nrow(group_neg), top_impact - pos_count)
    }

    # COMBINE TOP POSITIVE AND NEGATIVE IMPACTS
    filtered_group <- rbind(
      if (pos_count > 0) group_pos[order(-group_pos$Total), , drop = FALSE][seq_len(pos_count), , drop = FALSE] else NULL,
      if (neg_count > 0) group_neg[order(group_neg$Total), , drop = FALSE][seq_len(neg_count), , drop = FALSE] else NULL
    )

    filtered_total_list[[group]] <- filtered_group
  }

  # COMBINE FILTERED GROUPS
  filtered_total <- do.call(rbind, filtered_total_list)

  # GET X-AXIS VALUES TO KEEP
  keep_x_axis <- filtered_total[[x_axis_from]]

  # FILTER ORIGINAL DATA
  filtered_data <- data[data[[x_axis_from]] %in% keep_x_axis, ]

  # CLEAN UP
  filtered_total$._group_id_ <- NULL

  # SORT DATA IF POSSIBLE
  if (x_axis_from %in% names(filtered_data)) {
    avg_formula <- as.formula(paste("Value ~", x_axis_from))
    avg_values <- stats::aggregate(avg_formula, data = filtered_data, mean, na.rm = TRUE)
    sorted_groups <- avg_values[order(avg_values$Value), 1]
    filtered_data[[x_axis_from]] <- factor(filtered_data[[x_axis_from]], levels = sorted_groups)
  }

  return(filtered_data)
}

#' @title Export and Save GTAP Plots
#' @description
#' Handles exporting plots to PNG or PDF format using specified export settings.
#' Supports merged PDF export, individual export, and dynamic file naming.
#'
#' @param plots A ggplot2 object or a named list of ggplot2 objects to export.
#' @param output_path Character. Directory to save plots. If NULL, uses working directory.
#' @param export_picture Logical. If TRUE, exports PNG files (default: TRUE).
#' @param export_as_pdf Logical or "merged". If TRUE, exports individual PDFs. If "merged", combines all into one PDF.
#' @param export_config List. Export settings such as `width`, `height`, `dpi`, `bg`, `limitsize`, `file_name`.
#' @param data Data used for calculating fallback plot dimensions (optional).
#' @param panel_layout A list with `rows` and `cols` used for auto-scaling plot size (optional).
#' @param default_filename Character. Default base filename if none specified.
#'
#' @return Invisibly returns NULL. Exports plots to disk.
#' @keywords internal
#' @noRd
.export_plot_output <- function(plots, output_path = NULL, export_picture = TRUE,
                                export_as_pdf = FALSE, export_config = NULL,
                                data = NULL, panel_layout = NULL, default_filename = NULL) {
  # Prepare export configuration
  if (is.null(export_config)) {
    export_config <- list()
  }

  # Default export settings if not specified
  if (is.null(export_config$dpi)) export_config$dpi <- 300
  if (is.null(export_config$bg)) export_config$bg <- "white"
  if (is.null(export_config$limitsize)) export_config$limitsize <- FALSE

  # Handle custom dimensions if provided
  if (is.null(export_config$width) || is.null(export_config$height)) {
    dimensions <- .calculate_plot_dimensions(data, panel_layout)
    export_config$width <- dimensions$width
    export_config$height <- dimensions$height
  } else {
    dimensions <- list(width = export_config$width, height = export_config$height)
  }

  # Display dimensions at the start of export process
  .display_export_dimensions(dimensions, plots, "start", export_config$dpi)

  # Process output_path
  if (is.null(output_path)) {
    output_path <- tempdir()
    message("No output path specified. Using temporary directory: ", output_path)
  }

  # Normalize the path to handle spaces and special characters
  output_path <- normalizePath(output_path, mustWork = FALSE)

  # Create output directory first, before any file operations
  if (!dir.exists(output_path)) {
    tryCatch({
      dir.create(output_path, recursive = TRUE)
      if (!dir.exists(output_path)) {
        warning("Failed to create output directory: ", output_path)
        output_path <- tempdir()
        message("Using temporary directory instead: ", output_path)
      }
    }, error = function(e) {
      warning("Error creating output directory: ", conditionMessage(e))
      output_path <- tempdir()
      message("Using temporary directory instead: ", output_path)
    })
  }

  # Check if directory is writable
  if (file.access(output_path, 2) != 0) {
    warning("Output directory is not writable: ", output_path)
    output_path <- tempdir()
    message("Using temporary directory instead: ", output_path)

    # Ensure this directory exists
    if (!dir.exists(output_path)) {
      dir.create(output_path, recursive = TRUE)
    }
  }

  # Normalize plot names to be valid filenames
  is_single_plot <- inherits(plots, "gg")
  if (is_single_plot) {
    plots <- list(plot = plots)
  }

  if (!is.list(plots) || length(plots) == 0) {
    stop("plots must be a ggplot object or a non-empty list of ggplot objects")
  }

  if (!all(sapply(plots, function(p) inherits(p, "gg")))) {
    stop("All elements in plots must be ggplot objects")
  }

  # Make sure all plot names are valid filenames
  plots_with_clean_names <- list()
  for (i in seq_along(plots)) {
    original_name <- names(plots)[i]
    # Keep the original name but ensure no invalid characters
    # LEAVE % signs intact here
    clean_name <- gsub("[^a-zA-Z0-9_\\-\\. ()%]", "_", original_name)
    clean_name <- gsub("\\s+", " ", clean_name)
    clean_name <- trimws(clean_name)

    plots_with_clean_names[[clean_name]] <- plots[[i]]
  }
  plots <- plots_with_clean_names
  n_plots <- length(plots)

  is_merge_pdf <- FALSE
  if (is.character(export_as_pdf)) {
    if (tolower(export_as_pdf) == "merged") {
      is_merge_pdf <- TRUE
      export_as_pdf <- TRUE
    }
  }

  if (export_as_pdf) {
    if (is_merge_pdf && n_plots >= 1) {
      # For merged PDFs, determine the filename
      pdf_file_name <- NULL

      # Use user-provided file name if specified
      if (!is.null(export_config$file_name) && nzchar(export_config$file_name)) {
        pdf_file_name <- export_config$file_name
      } else {
        # Use default_filename passed from the calling function
        # This should be the plot type (comparison, detail, stack)
        pdf_file_name <- default_filename
      }

      # Add number of plots suffix if more than 1 plot
      if (n_plots > 1) {
        pdf_file_name <- paste0(pdf_file_name, "_", n_plots, "plots")
      }

      # Make sure the filename is safe
      pdf_file_name <- gsub("[^a-zA-Z0-9_\\-\\. ]", "_", pdf_file_name)
      pdf_file_name <- gsub("\\s+", " ", pdf_file_name)
      pdf_file_name <- trimws(pdf_file_name)

      # Create the full path
      pdf_path <- file.path(output_path, paste0(pdf_file_name, ".pdf"))

      # Use tryCatch to handle PDF creation errors
      tryCatch({
        grDevices::pdf(
          file = pdf_path,
          width = export_config$width,
          height = export_config$height,
          useDingbats = FALSE,
          title = pdf_file_name
        )

        for (i in seq_along(plots)) {
          tryCatch({
            grid::grid.draw(plots[[i]])
          }, error = function(e) {
            warning("Error drawing plot: ", conditionMessage(e))
          })
        }

        # Always make sure to close the device
        grDevices::dev.off()

        if (file.exists(pdf_path)) {
          message("Combined PDF exported to: ", pdf_path)
        } else {
          warning("PDF creation failed. The output file was not created.")
        }
      }, error = function(e) {
        # Make sure to close any open devices on error
        if (grDevices::dev.cur() > 1) {
          try(grDevices::dev.off(), silent = TRUE)
        }
        warning("Error exporting merged PDF: ", conditionMessage(e))
      })
    } else {
      # Individual PDF export - use the cleaned plot names
      for (i in seq_along(plots)) {
        p <- plots[[i]]
        plot_name <- names(plots)[[i]]

        # CRITICAL FIX: Escape % with %% for ggsave
        safe_name <- gsub("%", "%%", plot_name)

        # Create the full path
        pdf_path <- file.path(output_path, paste0(plot_name, ".pdf"))

        # Use tryCatch to handle ggsave errors
        tryCatch({
          # Use the escaped name for ggsave
          ggplot2::ggsave(
            filename = pdf_path,  # Use original path with %
            plot = p,
            device = "pdf",
            width = export_config$width,
            height = export_config$height,
            dpi = export_config$dpi,
            bg = export_config$bg,
            limitsize = export_config$limitsize
          )

          if (file.exists(pdf_path)) {
            message("PDF figure exported to: ", pdf_path)
          } else {
            warning("PDF creation failed for plot name: ", plot_name)
          }
        }, error = function(e) {
          warning("Error exporting PDF for plot '", plot_name, "': ", conditionMessage(e))
        })
      }
    }
  }

  if (export_picture) {
    # PNG export - use the cleaned plot names
    for (i in seq_along(plots)) {
      p <- plots[[i]]
      plot_name <- names(plots)[[i]]

      # CRITICAL FIX: Escape % with %% for ggsave
      safe_name <- gsub("%", "%%", plot_name)

      # Create the full path for final filepath
      png_path <- file.path(output_path, paste0(plot_name, ".png"))

      # Create the full path with escaped % for ggsave
      png_path_safe <- file.path(output_path, paste0(safe_name, ".png"))

      # Use tryCatch to handle ggsave errors
      tryCatch({
        # The key fix - use the path with escaped % signs
        ggplot2::ggsave(
          filename = png_path_safe,  # Use safe path with %% for ggsave
          plot = p,
          device = "png",
          width = export_config$width,
          height = export_config$height,
          dpi = export_config$dpi,
          bg = export_config$bg,
          limitsize = export_config$limitsize
        )

        # Check if file exists and report success
        if (file.exists(png_path)) {
          message("PNG figure exported to: ", png_path)
        } else {
          warning("PNG creation failed for plot name: ", plot_name)
        }
      }, error = function(e) {
        warning("Error exporting PNG for plot '", plot_name, "': ", conditionMessage(e))
      })
    }
  }

  # Display dimensions at the end of export process
  .display_export_dimensions(dimensions, plots, "end", export_config$dpi)

  # Always return NULL invisibly to suppress output
  return(invisible(NULL))
}

# COMPARISON PLOT SPECIFIC FUNCTIONS --------------------------------------

#' @title Create Single Comparison Plot
#' @description
#' Generates a single bar chart for comparison plots, using consistent axis, theme, and label formatting.
#'
#' @param data A data frame containing values to plot.
#' @param x_axis_from Character. Column used for x-axis categories.
#' @param plot_title Character. Title of the plot.
#' @param unit Character. Unit of measure.
#' @param panel_rows Integer. Number of rows in the facet layout.
#' @param panel_cols Integer. Number of columns in the facet layout.
#' @param panel_var Character. Facet column name (default: "Experiment").
#' @param invert_axis Logical. Whether to flip the axis for horizontal layout.
#' @param plot_style_config List. Plot styling configuration.
#'
#' @return A ggplot2 object representing the comparison plot.
#' @keywords internal
#' @noRd
.create_single_comparison_plot <- function(data, x_axis_from, plot_title, unit,
                                           panel_rows, panel_cols,
                                           panel_var = "Experiment",
                                           invert_axis = FALSE,
                                           plot_style_config = NULL) {

  # Get style configuration
  style_config <- if (!is.null(plot_style_config)) {
    plot_style_config
  } else {
    .calculate_plot_style_config(NULL, "default")
  }

  # Set up variables for plotting
  x_var <- x_axis_from
  facet_var <- panel_var

  # Sort the Data of X_AXIS_FROM
  data[[x_var]] <- factor(data[[x_var]], levels = unique(data[[x_var]]))

  n_panels <- length(unique(data[[facet_var]]))

  # Calculate Y-Axis Limits using common function
  y_limits <- .calculate_value_axis_limits(data, unit, style_config)

  # Format axis labels using common function
  axis_labels <- .format_axis_labels(unit, x_axis_from, style_config)
  y_axis_label <- axis_labels$y_label
  x_axis_label <- axis_labels$x_label

  # Calculate label positions
  if (invert_axis) {
    # For horizontal bars (coord_flip), need more space for labels
    label_position <- sapply(data$Value, function(x) {
      if (x >= 0) x + diff(y_limits) * 0.08 else x - diff(y_limits) * 0.08
    })
  } else {
    # For vertical bars, regular spacing
    label_position <- sapply(data$Value, function(x) {
      if (x >= 0) x + diff(y_limits) * 0.03 else x - diff(y_limits) * 0.03
    })
  }

  # Generate colors if provided
  if (!is.null(style_config$color_tone)) {
    palette_type <- if (!is.null(style_config$color_palette_type)) style_config$color_palette_type else "qualitative"
    color_palette <- .generate_comparison_colors(data, style_config$color_tone, x_var, palette_type)
  }

  # Get bar styling from config
  bar_width <- style_config$bar_width
  bar_spacing <- style_config$bar_spacing

  # Create the basic plot
  if (invert_axis) {
    # For horizontal bars (flipped coordinates)
    p <- ggplot2::ggplot(data, ggplot2::aes(
      y = .data[[x_var]],
      x = .data[["Value"]],
      fill = .data[[x_var]])) +
      ggplot2::geom_bar(stat = "identity",
                        position = ggplot2::position_dodge(width = bar_spacing),
                        width = bar_width)

    # Add value labels if configured
    if (style_config$show_value_labels) {
      decimal_places <- style_config$value_label_decimal_places
      value_size <- style_config$value_label_size

      p <- p + ggplot2::geom_text(
        ggplot2::aes(x = label_position,
                     label = sprintf(paste0("%.", decimal_places, "f"), .data[["Value"]])),
        position = ggplot2::position_dodge(width = bar_spacing),
        size = value_size,
        color = "black"
      )
    }

    # Apply scale to value axis (X-axis) using common function
    p <- p + .apply_axis_scale(y_limits, style_config, "x")

    # Add zero line if configured using common function
    if (style_config$show_zero_line) {
      p <- .add_zero_line(p, invert_axis, style_config)
    }
  } else {
    # For vertical bars (normal orientation)
    p <- ggplot2::ggplot(data, ggplot2::aes(
      x = .data[[x_var]],
      y = .data[["Value"]],
      fill = .data[[x_var]])) +
      ggplot2::geom_bar(stat = "identity",
                        position = ggplot2::position_dodge(width = bar_spacing),
                        width = bar_width)

    # Add value labels if configured
    if (style_config$show_value_labels) {
      decimal_places <- style_config$value_label_decimal_places
      value_size <- style_config$value_label_size

      p <- p + ggplot2::geom_text(
        ggplot2::aes(y = label_position,
                     label = sprintf(paste0("%.", decimal_places, "f"), .data[["Value"]])),
        position = ggplot2::position_dodge(width = bar_spacing),
        size = value_size,
        color = "black"
      )
    }

    # Apply scale to value axis (Y-axis) using common function
    p <- p + .apply_axis_scale(y_limits, style_config, "y")

    # Add zero line if configured using common function
    if (style_config$show_zero_line) {
      p <- .add_zero_line(p, invert_axis, style_config)
    }
  }

  # Apply colors if provided
  if (!is.null(style_config$color_tone)) {
    p <- p + ggplot2::scale_fill_manual(values = color_palette)
  }

  # Add facet wrap if we have multiple panels using common function
  if (n_panels > 1) {
    p <- p + .create_facet_wrap(panel_var = facet_var,
                                panel_rows = panel_rows,
                                panel_cols = panel_cols,
                                style_config = style_config)
  }

  # Apply theme styling
  p <- p + ggplot2::theme_minimal()

  # Set axis labels based on orientation using common function
  p <- .set_axis_labels(p, invert_axis, plot_title, x_axis_label, y_axis_label, style_config)

  # Apply style config
  p <- .apply_plot_style_config(p, style_config)

  return(p)
}

#' @title Generate Comparison Colors
#' @description
#' Creates a custom palette for bar plots based on number of categories and color tone.
#'
#' @param data A data frame.
#' @param color_tone Character. Palette base color or theme name.
#' @param axis_col Character. Column representing categories.
#' @param palette_type Character. Palette style (e.g., "qualitative").
#'
#' @return Named vector of hex color codes.
#' @keywords internal
#' @noRd
.generate_comparison_colors <- function(data, color_tone = NULL, axis_col, palette_type = "qualitative") {
  if(is.null(color_tone)) return(NULL)

  n_colors <- length(unique(data[[axis_col]]))

  # Try to get a themed palette first
  themed_palette <- .create_color_palette(color_tone = color_tone, n_colors = n_colors,
                                          palette_type = palette_type)

  if (!is.null(themed_palette)) {
    return(themed_palette)
  }

  # Fallback to custom color generation if needed
  base_color <- if(startsWith(color_tone, "#")) {
    color_tone
  } else {
    try(colorspace::hex(colorspace::sRGB(t(col2rgb(color_tone) / 255))), silent = TRUE)
  }

  if (inherits(base_color, "try-error")) {
    return(NULL)
  }

  base_rgb <- colorspace::hex2RGB(base_color)
  base_hcl <- as(base_rgb, "polarLUV")

  hue <- base_hcl@coords[, "H"]
  chroma_range <- seq(max(30, base_hcl@coords[, "C"] - 20),
                      min(100, base_hcl@coords[, "C"] + 20),
                      length.out = n_colors)
  luminance_range <- seq(max(30, base_hcl@coords[, "L"] - 20),
                         min(90, base_hcl@coords[, "L"] + 20),
                         length.out = n_colors)

  sapply(1:n_colors, function(i) {
    colorspace::hex(colorspace::polarLUV(L = luminance_range[i],
                                         C = chroma_range[i],
                                         H = hue))
  })
}

# DETAIL PLOT SPECIFIC FUNCTIONS -----------------------------------------

#' @title Create Single Detail Plot
#' @description
#' Generates a bar chart with color-coded bars based on value magnitude and polarity.
#' Used for detail-level visualization with top-impact filtering support.
#'
#' @param data A data frame containing plotting data.
#' @param x_axis_from Character. X-axis grouping variable.
#' @param plot_title Character. Title of the plot.
#' @param unit Character. Unit of measurement.
#' @param panel_rows Integer. Number of rows in the facet layout.
#' @param panel_cols Integer. Number of columns in the facet layout.
#' @param panel_var Character. Facet variable (default: "Experiment").
#' @param invert_axis Logical. Flip the plot orientation.
#' @param top_impact Numeric. Number of top-impact bars to display (optional).
#' @param plot_style_config List. Visual configuration.
#'
#' @return A ggplot2 object.
#' @keywords internal
#' @noRd
.create_single_detail_plot <- function(data, x_axis_from, plot_title, unit,
                                       panel_rows, panel_cols,
                                       panel_var = "Experiment",
                                       invert_axis = FALSE,
                                       top_impact = NULL,
                                       plot_style_config = NULL) {

  # Get style configuration
  style_config <- if (!is.null(plot_style_config)) {
    plot_style_config
  } else {
    .calculate_plot_style_config(NULL, "default")
  }

  # Setup color palette
  positive_color <- style_config$positive_color
  negative_color <- style_config$negative_color
  palette_type <- if (!is.null(style_config$color_palette_type)) style_config$color_palette_type else "qualitative"
  color_palette <- .generate_color_palette(positive_color, negative_color, style_config$color_tone, palette_type)

  # Prepare data
  max_abs_value <- max(abs(data$Value))
  decimal_places <- style_config$value_label_decimal_places
  data$Label <- sprintf(paste0("%.", decimal_places, "f"), data$Value)

  n_vars <- length(unique(data[[x_axis_from]]))
  n_panels <- length(unique(data[[panel_var]]))

  # Categorize values for color coding
  data <- .categorize_values_by_panel(data, panel_var)

  # Calculate Y-Axis Limits using common function
  y_limits <- .calculate_value_axis_limits(data, unit, style_config)

  # Format axis labels using common function
  axis_labels <- .format_axis_labels(unit, x_axis_from, style_config)
  y_axis_label <- axis_labels$y_label
  x_axis_label <- axis_labels$x_label

  # Prepare data for plotting
  if (!is.null(top_impact)) {
    data <- data[order(data$Value), ]
    x_factor_col <- paste0(x_axis_from, "_factor")
    data[[x_factor_col]] <- factor(data[[x_axis_from]], levels = unique(data[[x_axis_from]]))
  } else {
    x_factor_col <- paste0(x_axis_from, "_factor")
    if (is.factor(data[[x_axis_from]])) {
      data[[x_factor_col]] <- data[[x_axis_from]]
    } else {
      data[[x_factor_col]] <- factor(data[[x_axis_from]], levels = unique(data[[x_axis_from]]))
    }
  }

  # Create base plot with appropriate ordering
  if (invert_axis) {
    # For horizontal bars (flipped coordinates)
    p <- ggplot2::ggplot() +
      ggplot2::geom_hline(yintercept = 1:n_vars + 0.5, color = "gray70", linewidth = 0.4) +
      ggplot2::geom_col(
        data = data,
        mapping = ggplot2::aes(
          y = .data[[x_factor_col]],
          x = .data[["Value"]],
          fill = .data[["value_category"]]
        ),
        width = style_config$bar_width
      )

    # Add value labels for horizontal bars
    if (style_config$show_value_labels) {
      p <- p + ggplot2::geom_text(
        data = data,
        mapping = ggplot2::aes(
          y = .data[[x_factor_col]],
          x = .data[["Value"]],
          label = .data[["Label"]]
        ),
        hjust = ifelse(data$Value >= 0, -0.2, 1.2),
        size = style_config$value_label_size
      )
    }

    # Apply scale to x-axis using common function
    p <- p + .apply_axis_scale(y_limits, style_config, "x")

  } else {
    # For vertical bars (normal coordinates)
    p <- ggplot2::ggplot() +
      ggplot2::geom_vline(xintercept = 1:n_vars + 0.5, color = "gray70", linewidth = 0.4) +
      ggplot2::geom_col(
        data = data,
        mapping = ggplot2::aes(
          x = .data[[x_factor_col]],
          y = .data[["Value"]],
          fill = .data[["value_category"]]
        ),
        width = style_config$bar_width
      )

    # Add value labels for vertical bars
    if (style_config$show_value_labels) {
      p <- p + ggplot2::geom_text(
        data = data,
        mapping = ggplot2::aes(
          x = .data[[x_factor_col]],
          y = .data[["Value"]],
          label = .data[["Label"]]
        ),
        vjust = ifelse(data$Value >= 0, -0.5, 1.5),
        size = style_config$value_label_size
      )
    }

    # Apply scale to y-axis using common function
    p <- p + .apply_axis_scale(y_limits, style_config, "y")
  }

  # Setup plot appearance
  p <- p +
    ggplot2::scale_fill_manual(values = color_palette, guide = "none") +
    ggplot2::theme_minimal()

  # Add facets if needed using common function
  free_scales <- !is.null(top_impact) || style_config$show_axis_titles_on_all_facets
  if (n_panels > 1) {
    p <- p + .create_facet_wrap(panel_var = panel_var,
                                panel_rows = panel_rows,
                                panel_cols = panel_cols,
                                style_config = style_config,
                                free_scales = free_scales)
  }

  # Set axis labels based on orientation using common function
  p <- .set_axis_labels(p, invert_axis, plot_title, x_axis_label, y_axis_label, style_config)

  # Add zero line if configured using common function
  if (style_config$show_zero_line) {
    p <- .add_zero_line(p, invert_axis, style_config)
  }

  # Apply style config
  p <- .apply_plot_style_config(p, style_config)

  return(p)
}

#' @title Categorize Bar Colors by Value Intensity
#' @description
#' Assigns value categories for use in color-coding bars in detail plots.
#'
#' @param data A data frame.
#' @param panel_var Character. Column for facet panels.
#'
#' @return Modified data frame with a `value_category` column.
#' @keywords internal
#' @noRd
.categorize_values_by_panel <- function(data, panel_var) {
  n_panels <- length(unique(data[[panel_var]]))

  if (n_panels > 1) {
    # Split by panel for panel-specific categorization
    panel_groups <- split(data, data[[panel_var]])

    # Process each panel separately for relative color intensity
    result <- lapply(panel_groups, function(panel_data) {
      # Get max absolute value within this panel only
      max_abs_panel <- max(abs(panel_data$Value), na.rm = TRUE)

      # Categorize based on panel-specific thresholds
      panel_data$value_category <- dplyr::case_when(
        panel_data$Value > 0 & abs(panel_data$Value) >= 0.7 * max_abs_panel ~ "extreme_positive",
        panel_data$Value < 0 & abs(panel_data$Value) >= 0.7 * max_abs_panel ~ "extreme_negative",
        panel_data$Value > 0 ~ "normal_positive",
        panel_data$Value < 0 ~ "normal_negative",
        TRUE ~ "neutral"
      )

      return(panel_data)
    })

    # Combine results back together
    data <- do.call(rbind, result)
    rownames(data) <- NULL
  } else {
    # Single panel - use original calculation
    max_abs_value <- max(abs(data$Value), na.rm = TRUE)
    data$value_category <- dplyr::case_when(
      data$Value > 0 & abs(data$Value) >= 0.7 * max_abs_value ~ "extreme_positive",
      data$Value < 0 & abs(data$Value) >= 0.7 * max_abs_value ~ "extreme_negative",
      data$Value > 0 ~ "normal_positive",
      data$Value < 0 ~ "normal_negative",
      TRUE ~ "neutral"
    )
  }

  return(data)
}

#' @title Generate Positive/Negative Color Palette
#' @description
#' Generates a color palette for value categories in detail plots,
#' including fallbacks for custom tones and RGB lightening/darkening.
#'
#' @param positive_color Character. Color for positive values (default "#2E8B57").
#' @param negative_color Character. Color for negative values (default "#CD5C5C").
#' @param color_tone Character. Optional color tone theme.
#' @param palette_type Character. Type of palette to use ("qualitative", etc.).
#'
#' @return Named vector of hex colors for value categories.
#' @keywords internal
#' @noRd
.generate_color_palette <- function(positive_color = "#2E8B57", negative_color = "#CD5C5C",
                                    color_tone = NULL, palette_type = "qualitative") {
  # If color_tone is specified, we use it to generate colors instead of positive/negative colors
  if (!is.null(color_tone)) {
    mono_palette <- .create_color_palette(color_tone = color_tone, n_colors = 5,
                                          palette_type = palette_type)

    if (!is.null(mono_palette)) {
      if (length(mono_palette) >= 5) {
        # If we have at least 5 colors, use them directly
        return(c(
          "extreme_positive" = mono_palette[1],
          "normal_positive" = mono_palette[2],
          "extreme_negative" = mono_palette[3],
          "normal_negative" = mono_palette[4],
          "neutral" = mono_palette[5]
        ))
      } else if (length(mono_palette) == 1) {
        # For monochromatic palettes with a single color, use different lightness levels
        base_rgb <- col2rgb(mono_palette[1])

        # Calculate lighter and darker variants
        lighter <- function(rgb_val, factor = 0.3) {
          pmax(0, pmin(255, rgb_val + (255 - rgb_val) * factor))
        }

        darker <- function(rgb_val, factor = 0.3) {
          pmax(0, pmin(255, rgb_val * (1 - factor)))
        }

        # Create variants with different lightness levels
        lighter1 <- rgb(lighter(base_rgb[1], 0.3), lighter(base_rgb[2], 0.3), lighter(base_rgb[3], 0.3), maxColorValue = 255)
        lighter2 <- rgb(lighter(base_rgb[1], 0.6), lighter(base_rgb[2], 0.6), lighter(base_rgb[3], 0.6), maxColorValue = 255)
        darker1 <- rgb(darker(base_rgb[1], 0.3), darker(base_rgb[2], 0.3), darker(base_rgb[3], 0.3), maxColorValue = 255)
        darker2 <- rgb(darker(base_rgb[1], 0.6), darker(base_rgb[2], 0.6), darker(base_rgb[3], 0.6), maxColorValue = 255)

        return(c(
          "extreme_positive" = darker1,
          "normal_positive" = mono_palette[1],
          "extreme_negative" = darker2,
          "normal_negative" = lighter1,
          "neutral" = lighter2
        ))
      }
    }
  }

  # If we don't have a color_tone or couldn't generate a palette from it,
  # fall back to the traditional positive/negative colors
  adjust_shade <- function(color, factor = 0.7) {
    rgb_col <- col2rgb(color)
    lighter <- rgb_col + (255 - rgb_col) * (1 - factor)
    return(rgb(lighter[1], lighter[2], lighter[3], maxColorValue = 255))
  }

  c(
    "extreme_positive" = positive_color,
    "normal_positive" = adjust_shade(positive_color),
    "extreme_negative" = negative_color,
    "normal_negative" = adjust_shade(negative_color),
    "neutral" = "gray"
  )
}

# STACK PLOT SPECIFIC FUNCTIONS ------------------------------------------

#' @title Create Single Stacked Plot
#' @description
#' Generates a stacked bar chart for decomposition analysis with optional total labels.
#'
#' @param data A data frame for the stacked bars.
#' @param total_data A data frame containing totals to annotate.
#' @param x_axis_from Character. X-axis grouping variable.
#' @param stack_value_from Character. Column representing stacked components.
#' @param plot_title Character. Title for the plot.
#' @param unit Character. Unit of measurement.
#' @param panel_rows Integer. Number of panel rows for faceting.
#' @param panel_cols Integer. Number of panel columns for faceting.
#' @param panel_var Character. Facet variable (default: "Experiment").
#' @param show_total Logical. Whether to display total value labels.
#' @param invert_axis Logical. Flip bar orientation.
#' @param top_impact Numeric. Number of top impactful values (optional).
#' @param plot_style_config List. Visual style configuration.
#'
#' @return A ggplot2 object.
#' @keywords internal
#' @noRd
.create_single_stacked_plot <- function(data, total_data, x_axis_from, stack_value_from,
                                        plot_title, unit,
                                        panel_rows, panel_cols,
                                        panel_var = "Experiment",
                                        show_total = TRUE,
                                        invert_axis = FALSE,
                                        top_impact = NULL,
                                        plot_style_config = NULL) {

  # Get style configuration
  style_config <- if (!is.null(plot_style_config)) {
    plot_style_config
  } else {
    .calculate_plot_style_config(NULL, "default")
  }

  # Setup variables
  color_tone <- style_config$color_tone
  n_panels <- length(unique(data[[panel_var]]))

  # Respect existing factor levels for consistent sorting
  if (!is.null(top_impact)) {
    # For top_impact case, allow automatic sorting
  } else {
    # Preserve existing factor levels for x_axis_from if it's already a factor
    if (is.factor(data[[x_axis_from]])) {
      # Already a factor, keep levels as is
    } else {
      # If not a factor, create one with current ordering
      data[[x_axis_from]] <- factor(data[[x_axis_from]], levels = unique(data[[x_axis_from]]))
    }

    # Also preserve factor levels for stack_value_from
    if (is.factor(data[[stack_value_from]])) {
      # Already a factor, keep levels as is
    } else {
      # If not a factor, create one with current ordering
      data[[stack_value_from]] <- factor(data[[stack_value_from]], levels = unique(data[[stack_value_from]]))
    }
  }

  # Generate color palette
  palette_type <- if (!is.null(style_config$color_palette_type)) style_config$color_palette_type else "qualitative"
  color_palette <- .generate_stack_colors(data, stack_value_from, color_tone, palette_type)

  # Calculate Y Limits
  y_limit <- .calculate_stack_limits(total_data, style_config)

  # Format axis labels
  y_axis_label <- .format_y_axis_label(unit, style_config)
  x_axis_label <- if (!is.null(style_config$x_axis_description) && nzchar(style_config$x_axis_description)) {
    style_config$x_axis_description
  } else {
    x_axis_from
  }

  # Create base plot based on orientation
  if (invert_axis) {
    # For horizontal bars (categories on y-axis, values on x-axis)
    p <- ggplot2::ggplot() +
      ggplot2::geom_col(
        data = data,
        ggplot2::aes(
          y = .data[[x_axis_from]],
          x = .data[["Value"]],
          fill = .data[[stack_value_from]]
        ),
        position = "stack",
        width = style_config$bar_width
      )

    # Add total labels if configured
    if (show_total) {
      decimal_places <- style_config$value_label_decimal_places
      value_size <- style_config$value_label_size
      value_label_face <- style_config$value_label_face

      p <- p + ggplot2::geom_text(
        data = total_data,
        ggplot2::aes(
          y = .data[[x_axis_from]],
          x = ifelse(Total >= 0,
                     PositiveTotal + pmax(abs(Total) * 0.1, 0.1),
                     NegativeTotal - pmax(abs(Total) * 0.1, 0.1)),
          label = sprintf(paste0("Total\n%.", decimal_places, "f"), Total)
        ),
        hjust = ifelse(total_data$Total >= 0, 0, 1),
        vjust = 0.5,
        size = value_size,
        fontface = value_label_face
      )
    }

    # Apply scale to value axis (X-axis)
    p <- p + .apply_axis_scale(y_limit, style_config, "x")

  } else {
    # For vertical bars (categories on x-axis, values on y-axis)
    p <- ggplot2::ggplot() +
      ggplot2::geom_col(
        data = data,
        ggplot2::aes(
          x = .data[[x_axis_from]],
          y = .data[["Value"]],
          fill = .data[[stack_value_from]]
        ),
        position = "stack",
        width = style_config$bar_width
      )

    # Add total labels if configured
    if (show_total) {
      decimal_places <- style_config$value_label_decimal_places
      value_size <- style_config$value_label_size
      value_label_face <- style_config$value_label_face

      p <- p + ggplot2::geom_text(
        data = total_data,
        ggplot2::aes(
          x = .data[[x_axis_from]],
          y = ifelse(Total >= 0,
                     PositiveTotal + abs(Total) * 0.05,
                     NegativeTotal - abs(Total) * 0.05),
          label = sprintf(paste0("Total\n%.", decimal_places, "f"), Total)
        ),
        vjust = ifelse(total_data$Total >= 0, 0, 1.5),
        size = value_size,
        fontface = value_label_face
      )
    }

    # Apply scale to value axis (Y-axis)
    p <- p + .apply_axis_scale(y_limit, style_config, "y")
  }

  # Add zero line if configured
  if (style_config$show_zero_line) {
    p <- .add_zero_line(p, invert_axis, style_config)
  }

  # Add facets if needed
  if (n_panels > 1) {
    free_scales <- !is.null(top_impact) || style_config$show_axis_titles_on_all_facets
    p <- p + .create_facet_wrap(panel_var = panel_var,
                                panel_rows = panel_rows,
                                panel_cols = panel_cols,
                                style_config = style_config,
                                free_scales = free_scales)
  }

  # Setup appearance
  p <- p + ggplot2::scale_fill_manual(values = color_palette) +
    ggplot2::theme_minimal()

  # Set up legend
  if (style_config$show_legend) {
    p <- p + ggplot2::theme(
      legend.position = style_config$legend_position,
      legend.title = if (style_config$show_legend_title) {
        ggplot2::element_text(face = style_config$legend_title_face)
      } else {
        ggplot2::element_blank()
      },
      legend.text = ggplot2::element_text(
        face = style_config$legend_text_face,
        size = style_config$legend_text_size
      )
    )
  } else {
    p <- p + ggplot2::theme(legend.position = "none")
  }

  # Set axis labels based on orientation
  p <- .set_axis_labels(p, invert_axis, plot_title, x_axis_label, y_axis_label, style_config)

  # Apply style config
  p <- .apply_plot_style_config(p, style_config)

  return(p)
}

#' @title Create Single Unstacked Plot
#' @description
#' Generates a bar plot showing disaggregated components from stack plots.
#' Each `x_axis_from` value is plotted as a separate unstacked bar.
#'
#' @param data A data frame of values to plot.
#' @param total_data A data frame containing totals (optional).
#' @param x_axis_from Character. The grouping variable for bars.
#' @param stack_value_from Character. Component column (used for fill).
#' @param plot_title Character. Plot title.
#' @param unit Character. Unit of measurement.
#' @param panel_rows Integer. Facet layout rows.
#' @param panel_cols Integer. Facet layout columns.
#' @param panel_var Character. Facet grouping variable.
#' @param invert_axis Logical. Flip orientation.
#' @param top_impact Numeric. Number of top values to show (optional).
#' @param plot_style_config List. Style configuration.
#'
#' @return A ggplot2 object.
#' @keywords internal
#' @noRd
.create_single_unstacked_plot <- function(data, total_data, x_axis_from, stack_value_from,
                                          plot_title, unit,
                                          panel_rows, panel_cols,
                                          panel_var = "Experiment",
                                          invert_axis = FALSE,
                                          top_impact = NULL,
                                          plot_style_config = NULL) {

  # Get style configuration
  style_config <- if (!is.null(plot_style_config)) {
    plot_style_config
  } else {
    .calculate_plot_style_config(NULL, "default")
  }

  # Setup variables
  color_tone <- style_config$color_tone
  n_panels <- length(unique(data[[panel_var]]))

  # Respect existing factor levels for consistent sorting
  if (!is.null(top_impact)) {
    # For top_impact case, allow automatic sorting
  } else {
    # Preserve existing factor levels for x_axis_from if it's already a factor
    if (is.factor(data[[x_axis_from]])) {
      # Already a factor, keep levels as is
    } else {
      # If not a factor, create one with current ordering
      data[[x_axis_from]] <- factor(data[[x_axis_from]], levels = unique(data[[x_axis_from]]))
    }

    # Also preserve factor levels for stack_value_from
    if (is.factor(data[[stack_value_from]])) {
      # Already a factor, keep levels as is
    } else {
      # If not a factor, create one with current ordering
      data[[stack_value_from]] <- factor(data[[stack_value_from]], levels = unique(data[[stack_value_from]]))
    }
  }

  # Generate color palette
  palette_type <- if (!is.null(style_config$color_palette_type)) style_config$color_palette_type else "qualitative"
  color_palette <- .generate_stack_colors(data, stack_value_from, color_tone, palette_type)

  # Calculate Y Limits - for unstacked, use value range directly
  value_range <- range(data$Value, na.rm = TRUE)
  max_abs <- max(abs(value_range), na.rm = TRUE)

  if (!is.null(style_config$scale_limit) && length(style_config$scale_limit) == 2) {
    y_limit <- style_config$scale_limit
  } else {
    # Determine if we have all positive, all negative, or mixed values
    if (all(data$Value >= 0, na.rm = TRUE)) {
      # All positive values
      y_limit <- c(0, max_abs * 1.3)
    } else if (all(data$Value <= 0, na.rm = TRUE)) {
      # All negative values
      y_limit <- c(-max_abs * 1.3, 0)
    } else {
      # Mixed values - symmetrical with extra padding
      y_limit <- c(-max_abs * 1.3, max_abs * 1.3)
    }
  }

  # Format axis labels
  y_axis_label <- .format_y_axis_label(unit, style_config)
  x_axis_label <- if (!is.null(style_config$x_axis_description) && nzchar(style_config$x_axis_description)) {
    style_config$x_axis_description
  } else {
    stack_value_from
  }

  # Format value labels
  decimal_places <- style_config$value_label_decimal_places
  data$Label <- sprintf(paste0("%.", decimal_places, "f"), data$Value)

  # CREATE BASE PLOT BASED ON ORIENTATION
  if (invert_axis) {
    # For horizontal bars (categories on y-axis, values on x-axis)
    p <- ggplot2::ggplot() +
      ggplot2::geom_col(
        data = data,
        ggplot2::aes(
          y = .data[[stack_value_from]],
          x = .data[["Value"]],
          fill = .data[[stack_value_from]]
        ),
        width = style_config$bar_width
      )

    # ADD VALUE LABELS IF CONFIGURED
    if (style_config$show_value_labels) {
      p <- p + ggplot2::geom_text(
        data = data,
        ggplot2::aes(
          y = .data[[stack_value_from]],
          x = .data[["Value"]],
          label = .data[["Label"]]
        ),
        hjust = ifelse(data$Value >= 0, -0.2, 1.2),
        size = style_config$value_label_size
      )
    }

    # APPLY SCALE TO VALUE AXIS (X-AXIS)
    p <- p + .apply_axis_scale(y_limit, style_config, "x")

  } else {
    # For vertical bars (categories on x-axis, values on y-axis)
    p <- ggplot2::ggplot() +
      ggplot2::geom_col(
        data = data,
        ggplot2::aes(
          x = .data[[stack_value_from]],
          y = .data[["Value"]],
          fill = .data[[stack_value_from]]
        ),
        width = style_config$bar_width
      )

    # ADD VALUE LABELS IF CONFIGURED
    if (style_config$show_value_labels) {
      p <- p + ggplot2::geom_text(
        data = data,
        ggplot2::aes(
          x = .data[[stack_value_from]],
          y = .data[["Value"]],
          label = .data[["Label"]]
        ),
        vjust = ifelse(data$Value >= 0, -0.5, 1.5),
        size = style_config$value_label_size
      )
    }

    # APPLY SCALE TO VALUE AXIS (Y-AXIS)
    p <- p + .apply_axis_scale(y_limit, style_config, "y")
  }

  # ADD ZERO LINE IF CONFIGURED
  if (style_config$show_zero_line) {
    p <- .add_zero_line(p, invert_axis, style_config)
  }

  # ADD FACETS IF NEEDED
  if (n_panels > 1) {
    free_scales <- !is.null(top_impact) || style_config$show_axis_titles_on_all_facets
    p <- p + .create_facet_wrap(panel_var = panel_var,
                                panel_rows = panel_rows,
                                panel_cols = panel_cols,
                                style_config = style_config,
                                free_scales = free_scales)
  }

  # SETUP APPEARANCE
  p <- p + ggplot2::scale_fill_manual(values = color_palette) +
    ggplot2::theme_minimal()

  # HANDLE AXIS LABELS BASED ON ORIENTATION
  p <- .set_axis_labels(p, invert_axis, plot_title, x_axis_label, y_axis_label, style_config)

  # Apply style config
  p <- .apply_plot_style_config(p, style_config)

  return(p)
}

#' @title Calculate Stack Plot Y-Axis Limits
#' @description
#' Determines appropriate y-axis limits for stacked plots using the total data,
#' or returns fallback if totals are unavailable.
#'
#' @param total_data Data frame with precomputed totals.
#' @param style_config List. Contains user-specified `scale_limit` or default fallback.
#'
#' @return Numeric vector of length 2 indicating axis limits.
#' @keywords internal
#' @noRd
.calculate_stack_limits <- function(total_data, style_config) {
  # Use scale_limit if provided
  if (!is.null(style_config$scale_limit) && length(style_config$scale_limit) == 2) {
    return(style_config$scale_limit)
  }

  # Check if total_data exists and has the Total column
  if (is.data.frame(total_data) && nrow(total_data) > 0 && "Total" %in% names(total_data)) {
    # Calculate appropriate limits based on actual values
    max_abs_total <- max(abs(total_data$Total), na.rm = TRUE)

    if (is.finite(max_abs_total) && max_abs_total > 0) {
      if (all(total_data$Total >= 0, na.rm = TRUE)) {
        # All positive values
        return(c(0, max(total_data$Total, na.rm = TRUE) * 1.4))
      } else if (all(total_data$Total <= 0, na.rm = TRUE)) {
        # All negative values
        return(c(min(total_data$Total, na.rm = TRUE) * 1.4, 0))
      } else {
        # Mixed values
        return(c(min(total_data$Total, na.rm = TRUE) * 1.4,
                 max(total_data$Total, na.rm = TRUE) * 1.4))
      }
    } else {
      # Handle invalid data within total_data
      return(c(-10, 10))  # Fallback
    }
  }

  # Fallback if total_data is invalid
  return(c(-10, 10))
}

#' @title Generate Stack Plot Colors
#' @description
#' Generates a color palette for stacked bar components using tone, palette type,
#' and fallback logic for high contrast.
#'
#' @param data A data frame.
#' @param stack_value_from Character. Column name for stack components.
#' @param color_tone Character. Optional color theme.
#' @param palette_type Character. Palette type (e.g., "qualitative").
#'
#' @return Named vector of hex color codes for each stack category.
#' @keywords internal
#' @noRd
.generate_stack_colors <- function(data, stack_value_from, color_tone = NULL, palette_type = "qualitative") {
  components <- unique(data[[stack_value_from]])
  n_components <- length(components)

  if (n_components <= 1) {
    return(setNames(c("#4477AA"), components))
  }

  # Try to generate a diverse palette based on the provided color_tone
  themed_palette <- .create_color_palette(color_tone = color_tone, n_colors = n_components,
                                          palette_type = palette_type)

  if (!is.null(themed_palette)) {
    # Modify colors to increase distinction in the stack plot
    adjusted_colors <- try(colorspace::lighten(themed_palette, amount = seq(0.1, 0.5, length.out = n_components)), silent = TRUE)
    if (!inherits(adjusted_colors, "try-error")) {
      return(setNames(adjusted_colors, components))
    }
    return(setNames(themed_palette, components))
  }

  # If color_tone is a standard color, generate variations with more differentiation
  if (!is.null(color_tone)) {
    tryCatch({
      base_col <- grDevices::col2rgb(color_tone) / 255  # Normalize to 0-1
      hue_shifts <- seq(0, 360, length.out = n_components + 1)[-1]  # Rotate hues
      saturation_shifts <- seq(0.6, 1, length.out = n_components)  # Vary saturation

      colors <- sapply(seq_len(n_components), function(i) {
        hcl_col <- colorspace::HLS(base_col[1,1] * 360, base_col[2,1], base_col[3,1])
        grDevices::hcl(hue = (hcl_col@coords[1] + hue_shifts[i]) %% 360,
                       chroma = saturation_shifts[i] * 100,
                       luminance = hcl_col@coords[3] * 100)
      })

      return(setNames(colors, components))
    }, error = function(e) {
      # Fallback in case of any issues
    })
  }

  # Default High-Contrast Palette for Stack Plot
  default_palette <- c("#E41A1C", "#377EB8", "#4DAF4A", "#984EA3", "#FF7F00", "#FFFF33", "#A65628", "#F781BF", "#999999")

  if (n_components > length(default_palette)) {
    default_palette <- colorspace::rainbow_hcl(n_components, c = 100, l = 65)
  }

  return(setNames(default_palette[1:n_components], components))
}

# COMMON COLOR AND STYLING FUNCTIONS --------------------------------------

#' @title Create Themed or Monochrome Color Palette
#' @description
#' Generates a vector of colors using predefined themes or a specified base color.
#' Supports palette types like "qualitative", "sequential", and "diverging".
#'
#' @param color_tone Character. Theme name or base color (e.g., "gtap", "blue").
#' @param n_colors Integer. Number of colors required.
#' @param palette_type Character. Type of palette ("qualitative", "sequential", "diverging", "mono").
#'
#' @return A character vector of hex color codes.
#' @keywords internal
#' @noRd
.create_color_palette <- function(color_tone = NULL, n_colors = 5, palette_type = "qualitative") {
  # Special case for "mono" palette type - return exact same color for all items
  if (!is.null(palette_type) && tolower(palette_type) == "mono") {
    if (!is.null(color_tone)) {
      # Try to validate if it's a valid R color
      tryCatch({
        # Confirm it's a valid color
        base_col <- grDevices::col2rgb(color_tone)
        # Convert to hex for consistency
        hex_color <- grDevices::rgb(base_col[1,1], base_col[2,1], base_col[3,1], maxColorValue = 255)
        # Return the exact same color n_colors times
        return(rep(hex_color, n_colors))
      }, error = function(e) {
        warning(paste("Invalid color:", color_tone, "- using black instead"))
        return(rep("#000000", n_colors))
      })
    } else {
      # If no color_tone provided with mono, default to black
      return(rep("#000000", n_colors))
    }
  }

  # Rest of the original function for themed palettes
  themed_palettes <- list(
    academic = list(
      qualitative = c("#4477AA", "#66CCEE", "#228833", "#CCBB44", "#EE6677", "#AA3377", "#BBBBBB"),
      sequential = c("#FFF5EB", "#FEE6CE", "#FDD0A2", "#FDAE6B", "#FD8D3C", "#F16913", "#D94801", "#A63603", "#7F2704"),
      diverging = c("#2166AC", "#4393C3", "#92C5DE", "#D1E5F0", "#F7F7F7", "#FDDBC7", "#F4A582", "#D6604D", "#B2182B")
    ),
    purdue = list(
      qualitative = c("#9D9E9E", "#DAAA00", "#C28E0E", "#000000", "#7A6E0B", "#98700D", "#4D4038"),
      sequential = c("#FFFFFF", "#F6F0D8", "#EBE1B2", "#E2D48E", "#DAAA00", "#C28E0E", "#98700D", "#7A6E0B", "#000000"),
      diverging = c("#000000", "#4D4038", "#98700D", "#C28E0E", "#DAAA00", "#E2D48E", "#EBE1B2", "#F6F0D8", "#FFFFFF")
    ),
    colorblind = list(
      qualitative = c("#000000", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7"),
      sequential = c("#FFFFD9", "#EDF8B1", "#C7E9B4", "#7FCDBB", "#41B6C4", "#1D91C0", "#225EA8", "#253494", "#081D58"),
      diverging = c("#3288BD", "#66C2A5", "#ABDDA4", "#E6F598", "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F")
    ),
    economic = list(
      qualitative = c("#1B9E77", "#D95F02", "#7570B3", "#E7298A", "#66A61E", "#E6AB02", "#A6761D", "#666666"),
      sequential = c("#FFF7EC", "#FEE8C8", "#FDD49E", "#FDBB84", "#FC8D59", "#EF6548", "#D7301F", "#B30000", "#7F0000"),
      diverging = c("#1A1A40", "#306BAC", "#84B5D5", "#DEF5F7", "#FEFEFE", "#FEDEBE", "#DB7352", "#A6313A", "#67001F")
    ),
    trade = list(
      qualitative = c("#1F77B4", "#FF7F0E", "#2CA02C", "#D62728", "#9467BD", "#8C564B", "#E377C2", "#7F7F7F"),
      sequential = c("#F7FCF5", "#E5F5E0", "#C7E9C0", "#A1D99B", "#74C476", "#41AB5D", "#238B45", "#006D2C", "#00441B"),
      diverging = c("#2166AC", "#4393C3", "#92C5DE", "#D1E5F0", "#F7F7F7", "#FDDBC7", "#F4A582", "#D6604D", "#B2182B")
    ),
    gtap = list(
      qualitative = c("#003366", "#0055A4", "#009CDE", "#F2A900", "#666666", "#000000"),
      sequential = c("#FFFFFF", "#D6E6F7", "#A0C4E8", "#6AA2D8", "#347FC8", "#0055A4", "#003F88", "#002A66", "#000000"),
      diverging = c("#000000", "#003366", "#0055A4", "#347FC8", "#009CDE", "#A0C4E8", "#D6E6F7", "#F2A900", "#FFFFFF")
    ),
    gtap2 = list(
      qualitative = c("#002F5F", "#0072C6", "#00A3E0", "#F2A900", "#C28E0E", "#666666", "#4D4038"),
      sequential = c("#FFFFFF", "#E1EFF7", "#B3DFF0", "#7FC6E0", "#009CDE", "#DAAA00", "#C28E0E", "#98700D", "#000000"),
      diverging = c("#000000", "#4D4038", "#666666", "#0055A4", "#009CDE", "#00A3E0", "#DAAA00", "#E2D48E", "#FFFFFF")
    ),
    earth = list(
      qualitative = c("#8B4513", "#A0522D", "#CD853F", "#D2B48C", "#8FBC8F", "#556B2F", "#2E8B57"),
      sequential = c("#F5F5DC", "#E3DAC9", "#C4A484", "#A67B5B", "#806040", "#594028", "#3B2F2F"),
      diverging = c("#8B0000", "#B22222", "#CD5C5C", "#D3D3D3", "#4682B4", "#1E90FF", "#00008B")
    ),
    vibrant = list(
      qualitative = c("#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3"),
      sequential = c("#FFE5B4", "#FFCC66", "#FF9933", "#FF6600", "#FF3300", "#CC0000", "#990000"),
      diverging = c("#800000", "#FF4500", "#FFD700", "#FFFFFF", "#32CD32", "#008080", "#000080")
    ),
    bright = list(
      qualitative = c("#FF69B4", "#FF4500", "#FFD700", "#32CD32", "#00FFFF", "#1E90FF", "#8A2BE2"),
      sequential = c("#FFFACD", "#FFD700", "#FFA500", "#FF4500", "#DC143C", "#8B0000", "#4B0082"),
      diverging = c("#D2691E", "#FFA07A", "#FFDEAD", "#FFFFFF", "#ADD8E6", "#4682B4", "#00008B")
    ),
    minimal = list(
      qualitative = c("#222222", "#444444", "#666666", "#888888", "#AAAAAA", "#CCCCCC", "#EEEEEE"),
      sequential = c("#F8F9FA", "#E9ECEF", "#DEE2E6", "#CED4DA", "#ADB5BD", "#6C757D", "#343A40"),
      diverging = c("#5A5A5A", "#878787", "#B4B4B4", "#FFFFFF", "#CCCCCC", "#888888", "#444444")
    ),
    energetic = list(
      qualitative = c("#FF0000", "#FFAA00", "#FFFF00", "#00FF00", "#00AAAA", "#0000FF", "#5500AA"),
      sequential = c("#FFF5E6", "#FFDAB9", "#FFB07F", "#FF7F50", "#FF4500", "#DC143C", "#8B0000"),
      diverging = c("#8B0000", "#FF4500", "#FFD700", "#FFFFFF", "#00FFFF", "#0000FF", "#4B0082")
    ),
    pastel = list(
      qualitative = c("#FFB6C1", "#FFDAC1", "#FAFAD2", "#C1E1C1", "#B0E0E6", "#DDA0DD", "#E6E6FA"),
      sequential = c("#FFF5EE", "#FFE4E1", "#FFC0CB", "#FFB6C1", "#DB7093", "#C71585", "#800080"),
      diverging = c("#CD5C5C", "#FFA07A", "#FFDAB9", "#FFFFFF", "#ADD8E6", "#4682B4", "#00008B")
    ),
    spring = list(
      qualitative = c("#FF69B4", "#FFB6C1", "#FFD700", "#32CD32", "#87CEEB", "#9370DB", "#8B008B"),
      sequential = c("#FFF0F5", "#FFDAB9", "#FFC0CB", "#FFB6C1", "#FF69B4", "#DB7093", "#8B008B"),
      diverging = c("#FF4500", "#FFD700", "#FFFACD", "#FFFFFF", "#00FF7F", "#20B2AA", "#008080")
    ),
    summer = list(
      qualitative = c("#FF4500", "#FFA500", "#FFD700", "#00FF00", "#00CED1", "#1E90FF", "#8A2BE2"),
      sequential = c("#FFEBCD", "#FFD700", "#FFA500", "#FF4500", "#DC143C", "#8B0000", "#4B0082"),
      diverging = c("#FF4500", "#FFD700", "#FFFACD", "#FFFFFF", "#00CED1", "#1E90FF", "#00008B")
    ),
    winter = list(
      qualitative = c("#00FFFF", "#4682B4", "#87CEEB", "#5F9EA0", "#B0E0E6", "#ADD8E6", "#E0FFFF"),
      sequential = c("#FFFFFF", "#E0FFFF", "#B0E0E6", "#87CEEB", "#4682B4", "#4169E1", "#00008B"),
      diverging = c("#00008B", "#1E90FF", "#87CEEB", "#FFFFFF", "#FFDAB9", "#FF6347", "#8B0000")
    ),
    fall = list(
      qualitative = c("#FF4500", "#D2691E", "#8B4513", "#A0522D", "#CD853F", "#F4A460", "#FFD700"),
      sequential = c("#FFE4B5", "#FFD700", "#FFA500", "#FF8C00", "#D2691E", "#A0522D", "#8B0000"),
      diverging = c("#8B0000", "#D2691E", "#FFA07A", "#FFFFFF", "#87CEFA", "#4682B4", "#00008B")
    )
  )

  # Check if color_tone is a recognized theme
  if (!is.null(color_tone) && tolower(color_tone) %in% names(themed_palettes)) {
    palette <- themed_palettes[[tolower(color_tone)]][[palette_type]]

    # Ensure we have the right number of colors
    if (length(palette) < n_colors) {
      palette <- grDevices::colorRampPalette(palette)(n_colors)
    } else if (length(palette) > n_colors) {
      palette <- palette[1:n_colors]
    }

    return(palette)
  }

  # For mono-color themes that aren't predefined
  if (!is.null(color_tone) && grepl("_mono$", color_tone)) {
    # Extract base color name (remove _mono suffix)
    base_color <- sub("_mono$", "", color_tone)

    # Get the representative color
    color_hex <- NULL

    # If base_color is a named color like "blue", "red", etc.
    if (base_color %in% colors()) {
      color_hex <- base_color
    }
    # If base_color is a hex code
    else if (grepl("^#[0-9A-Fa-f]{6}$", base_color)) {
      color_hex <- base_color
    }
    # If base_color is one of our predefined palettes, use its first color
    else if (base_color %in% names(themed_palettes)) {
      color_hex <- themed_palettes[[base_color]]$qualitative[1]
    }
    # Default fallback
    else {
      color_hex <- "#000000"  # Default to black if color not recognized
    }

    # For _mono suffix, return the exact same color for all items
    return(rep(color_hex, n_colors))
  }

  # Try to interpret any standard R color
  if (!is.null(color_tone)) {
    tryCatch({
      # Try to validate if it's a valid R color
      base_col <- grDevices::col2rgb(color_tone)

      # If we get here, it's a valid color - create a palette of shades
      if (palette_type == "diverging") {
        darken_factor <- seq(0.4, 1.3, length.out = n_colors)
      } else {
        darken_factor <- seq(0.5, 1.5, length.out = n_colors)
      }

      # Create different shades based on the base color
      colors <- sapply(darken_factor, function(factor) {
        r <- min(255, max(0, base_col[1,1] * factor))
        g <- min(255, max(0, base_col[2,1] * factor))
        b <- min(255, max(0, base_col[3,1] * factor))
        grDevices::rgb(r, g, b, maxColorValue = 255)
      })

      return(colors)
    }, error = function(e) {
      # Color wasn't valid, return NULL
      return(NULL)
    })
  }

  return(NULL)
}

#' @title Calculate Plot Style Configuration
#' @description
#' Merges user-supplied style settings with GTAPViz defaults. Adjusts font sizes if `all_font_size` is used.
#'
#' @param config Optional list of user-defined style settings.
#' @param plot_type Character. Type of plot (default = "default").
#'
#' @return A complete list of style configuration parameters.
#' @keywords internal
#' @noRd
.calculate_plot_style_config <- function(config = NULL, plot_type = "default") {
  # Get default configuration from create_plot_style
  default_config <- list(
    # Title settings
    show_title = TRUE,
    title_face = "bold",
    title_size = 20,
    title_hjust = 0.5,
    add_unit_to_title = TRUE,
    title_margin = c(10, 0, 10, 0),
    title_format = list(type = "standard", text = "", sep = ""),

    # X-Axis settings
    show_x_axis_title = TRUE,
    x_axis_title_face = "bold",
    x_axis_title_size = 16,
    x_axis_title_margin = c(25, 25, 0, 0),
    show_x_axis_labels = TRUE,
    x_axis_text_face = "plain",
    x_axis_text_size = 14,
    x_axis_text_angle = 0,
    x_axis_text_hjust = 0,
    x_axis_description = "",

    # Y-Axis settings
    show_y_axis_title = TRUE,
    y_axis_title_face = "bold",
    y_axis_title_size = 16,
    y_axis_title_margin = c(25, 25, 0, 0),
    show_y_axis_labels = TRUE,
    y_axis_text_face = "plain",
    y_axis_text_size = 14,
    y_axis_text_angle = 0,
    y_axis_text_hjust = 0,
    y_axis_description = "",
    show_axis_titles_on_all_facets = TRUE,

    # Value label settings
    show_value_labels = TRUE,
    value_label_face = "plain",
    value_label_size = 5,
    value_label_position = "above",
    value_label_decimal_places = 2,

    # Legend settings
    show_legend = FALSE,
    show_legend_title = FALSE,
    legend_position = "bottom",
    legend_title_face = "bold",
    legend_text_face = "plain",
    legend_text_size = 14,

    # Panel strip settings
    strip_face = "bold",
    strip_text_size = 16,
    strip_background = "lightgrey",
    strip_text_margin = c(10, 0, 10, 0),

    # Panel layout
    panel_spacing = 2,
    panel_rows = NULL,
    panel_cols = NULL,
    theme = NULL,

    # Color settings
    color_tone = NULL,
    color_palette_type = "qualitative",
    positive_color = "#2E8B57",
    negative_color = "#CD5C5C",
    background_color = "white",
    grid_color = "grey90",
    show_grid_major_x = FALSE,
    show_grid_major_y = FALSE,
    show_grid_minor_x = FALSE,
    show_grid_minor_y = FALSE,

    # Zero line settings
    show_zero_line = TRUE,
    zero_line_type = "dashed",
    zero_line_color = "black",
    zero_line_size = 0.5,
    zero_line_position = 0,

    # Bar chart settings
    bar_width = 0.9,
    bar_spacing = 0.9,

    # Scale settings
    scale_limit = NULL,
    scale_increment = NULL,

    # Scale expansion settings
    expansion_y_mult = c(0.05, 0.1),
    expansion_x_mult = c(0.05, 0.05),

    # Font size settings
    all_font_size = 1,

    # Data sorting
    sort_data_by_value = FALSE,

    # Plot margin
    plot.margin = c(10, 25, 10, 10)
  )

  # If no config is provided, return the default
  if (is.null(config)) {
    return(default_config)
  }

  # Merge user config with defaults (user settings take precedence)
  final_config <- utils::modifyList(default_config, config)

  # Override font sizes with all_font_size if provided
  if (!is.null(final_config$all_font_size)) {
    font_sizes <- .calculate_font_sizes(NULL, NULL, final_config$all_font_size)

    # Only override font sizes if not explicitly set in user config
    font_size_fields <- c(
      "title_size", "x_axis_title_size", "y_axis_title_size",
      "strip_text_size", "x_axis_text_size", "y_axis_text_size",
      "legend_text_size", "value_label_size"
    )

    for (field in font_size_fields) {
      if (is.null(config) || is.null(config[[field]])) {
        final_config[[field]] <- font_sizes[[field]]
      }
    }
  }

  return(final_config)
}

#' @title Calculate Font Sizes for Plot Text
#' @description
#' Computes proportional font sizes based on a global multiplier `all_font_size`.
#'
#' @param width Numeric or NULL. Plot width (optional).
#' @param height Numeric or NULL. Plot height (optional).
#' @param all_font_size Numeric. Scaling factor for font size (default = 1).
#'
#' @return A list of named font sizes.
#' @keywords internal
#' @noRd
.calculate_font_sizes <- function(width, height, all_font_size = 1) {
  # Calculate scaling factor based on all_font_size
  factor <- all_font_size

  # Define proportional font sizes at reference level of all_font_size
  font_sizes <- list(
    title_size = round(20 * factor),
    x_axis_title_size = round(14 * factor),
    y_axis_title_size = round(14 * factor),
    strip_text_size = round(16 * factor),
    x_axis_text_size = round(12 * factor),
    y_axis_text_size = round(12 * factor),
    legend_title_size = round(14 * factor),
    legend_text_size = round(10 * factor),
    value_label_size = round(5 * factor)
  )

  return(font_sizes)
}

#' @title Apply Plot Style Configuration
#' @description
#' Applies margins, labels, fonts, themes, and grid visibility to a ggplot object
#' based on a given style configuration list.
#'
#' @param p A ggplot2 object.
#' @param config A list of style parameters as generated by `.calculate_plot_style_config`.
#'
#' @return A ggplot2 object with styles applied.
#' @keywords internal
#' @noRd
.apply_plot_style_config <- function(p, config) {
  # Helper function to convert numeric vector to margin object
  vector_to_margin <- function(vec) {
    if (is.numeric(vec) && length(vec) == 4) {
      return(ggplot2::margin(t = vec[1], r = vec[2], b = vec[3], l = vec[4]))
    }
    return(vec)
  }

  # Convert numeric margin vectors to ggplot2 margin objects
  title_margin <- vector_to_margin(config$title_margin)
  x_title_margin <- vector_to_margin(config$x_axis_title_margin)
  y_title_margin <- vector_to_margin(config$y_axis_title_margin)
  strip_margin <- vector_to_margin(config$strip_text_margin)
  plot_margin <- vector_to_margin(config$plot.margin)

  # Apply theme modifications
  p <- p + ggplot2::theme(
    # Title settings
    plot.title = if (config$show_title) {
      ggplot2::element_text(
        hjust = config$title_hjust,
        size = config$title_size,
        face = config$title_face,
        margin = title_margin
      )
    } else {
      ggplot2::element_blank()
    },

    # X axis title settings
    axis.title.x = if (config$show_x_axis_title) {
      ggplot2::element_text(
        size = config$x_axis_title_size,
        face = config$x_axis_title_face,
        margin = x_title_margin
      )
    } else {
      ggplot2::element_blank()
    },

    # Y axis title settings
    axis.title.y = if (config$show_y_axis_title) {
      ggplot2::element_text(
        size = config$y_axis_title_size,
        face = config$y_axis_title_face,
        margin = y_title_margin
      )
    } else {
      ggplot2::element_blank()
    },

    # X axis text settings
    axis.text.x = if (config$show_x_axis_labels) {
      ggplot2::element_text(
        size = config$x_axis_text_size,
        face = config$x_axis_text_face,
        angle = config$x_axis_text_angle,
        hjust = config$x_axis_text_hjust
      )
    } else {
      ggplot2::element_blank()
    },

    # Y axis text settings
    axis.text.y = if (config$show_y_axis_labels) {
      ggplot2::element_text(
        size = config$y_axis_text_size,
        face = config$y_axis_text_face,
        angle = config$y_axis_text_angle,
        hjust = config$y_axis_text_hjust
      )
    } else {
      ggplot2::element_blank()
    },

    # Legend settings
    legend.position = if (config$show_legend) config$legend_position else "none",
    legend.title = if (config$show_legend_title) {
      ggplot2::element_text(face = config$legend_title_face)
    } else {
      ggplot2::element_blank()
    },
    legend.text = ggplot2::element_text(
      face = config$legend_text_face,
      size = config$legend_text_size
    ),

    # Panel strip settings
    strip.text = ggplot2::element_text(
      face = config$strip_face,
      size = config$strip_text_size,
      margin = strip_margin
    ),
    strip.background = ggplot2::element_rect(fill = config$strip_background),

    # Panel spacing
    panel.spacing.x = ggplot2::unit(config$panel_spacing, "cm"),

    # Background and grid settings
    plot.background = ggplot2::element_rect(fill = config$background_color, color = NA),
    panel.background = ggplot2::element_rect(fill = config$background_color, color = NA),
    panel.grid.major.x = if (config$show_grid_major_x) {
      ggplot2::element_line(color = config$grid_color)
    } else {
      ggplot2::element_blank()
    },
    panel.grid.major.y = if (config$show_grid_major_y) {
      ggplot2::element_line(color = config$grid_color)
    } else {
      ggplot2::element_blank()
    },
    panel.grid.minor.x = if (config$show_grid_minor_x) {
      ggplot2::element_line(color = config$grid_color)
    } else {
      ggplot2::element_blank()
    },
    panel.grid.minor.y = if (config$show_grid_minor_y) {
      ggplot2::element_line(color = config$grid_color)
    } else {
      ggplot2::element_blank()
    }
  )

  # Explicitly apply plot margin as a separate theme element to ensure it's not overridden
  p <- p + ggplot2::theme(plot.margin = plot_margin)

  # Apply custom theme if provided
  if (!is.null(config$theme)) {
    p <- p + config$theme
  }

  return(p)
}

#' @title Display Export Dimensions in Console
#' @description
#' Outputs width, height, DPI, and number of plots before and after the export process.
#'
#' @param dimensions List with `width` and `height` values.
#' @param plots ggplot object or list of ggplots.
#' @param phase Character. "start" or "end" to indicate export phase.
#' @param dpi Numeric. Resolution in dots per inch (default = 300).
#'
#' @return Invisible NULL. Only used for messaging.
#' @keywords internal
#' @noRd
.display_export_dimensions <- function(dimensions, plots, phase = "start", dpi = 300) {
  if (!is.list(dimensions) || is.null(dimensions$width) || is.null(dimensions$height)) {
    return(invisible(NULL))
  }

  num_plots <- if (inherits(plots, "gg")) 1 else length(plots)

  if (phase == "start") {
    message(sprintf(">>> Starting plot export process: %d plot(s) with dimensions (widthxheight): %.1f x %.1f inches",
                    num_plots, dimensions$width, dimensions$height))
    message(sprintf(">>> DPI: %d", dpi))
  } else if (phase == "end") {
    message(sprintf(">>> Completed plot export: %d plot(s) exported with dimensions (widthxheight): %.1f x %.1f inches",
                    num_plots, dimensions$width, dimensions$height))
    message(sprintf(">>> DPI: %d", dpi))
  }

  invisible(NULL)
}

# TITLE HANDLING FUNCTIONS ------------------------------------------

#' @title Handle Plot Title and Export Name Generation
#' @description
#' Constructs a dynamic or static plot title and a clean file-safe export name
#' using variable labels, split/grouping values, unit, panel ID, and style config.
#'
#' @param var_name Character. Name of the variable being plotted.
#' @param sep_value Character. Split/grouping value for plot title.
#' @param x_value Character. Value used for unstacked plots.
#' @param plot_type Character. Plot type ("comparison", "detail", "stack", "unstack").
#' @param is_macro_mode Logical. Indicates macro-level aggregation (TRUE = one plot).
#' @param split_by Character or NULL. Column(s) used to split plots.
#' @param x_axis_from Character. Column name used on x-axis.
#' @param variable_col Character. Name of the variable column.
#' @param unit_name Character. Label for unit (e.g., "percent", "USD").
#' @param style_config List. List of plot title and style options.
#' @param data A data frame to optionally use for dynamic title rendering.
#' @param separate_figure Logical. If TRUE, individual plot per panel value.
#' @param panel_val Character. Name of panel to be added as suffix (if applicable).
#'
#' @return A list with `title` (plot title) and `export_name` (safe filename).
#' @keywords internal
#' @noRd
.handle_plot_title_and_export <- function(var_name = NULL, sep_value = NULL, x_value = NULL,
                                          plot_type = NULL, is_macro_mode = FALSE, split_by = NULL,
                                          x_axis_from = NULL, variable_col = NULL, unit_name = NULL,
                                          style_config = NULL, data = NULL, separate_figure = FALSE,
                                          panel_val = NULL, panel_var = "Experiment") {  # Added panel_var parameter

  # Generate basic title without panel value
  if (is_macro_mode) {
    plot_title <- .coalesce(
      var_name,
      .coalesce(
        if (!is.null(data) && !is.null(variable_col) && variable_col %in% names(data))
          unique(data[[variable_col]])[1],
        "Global Economic Impacts"
      )
    )
  } else {
    plot_title <- if (!is.null(sep_value) && !is.null(var_name)) {
      # Check if sep_value and var_name are identical to avoid duplication
      if (identical(as.character(sep_value), as.character(var_name))) {
        var_name  # Use just one value to avoid duplication
      } else {
        paste0(sep_value, " - ", var_name)
      }
    } else if (!is.null(sep_value)) {
      sep_value
    } else if (!is.null(var_name)) {
      var_name
    } else {
      "GTAP Analysis"
    }

    # Add x_value for unstack plots
    if (!is.null(x_value) && plot_type %in% c("unstack", "stack")) {
      plot_title <- paste0(plot_title, " - ", x_value)
    }
  }

  # Apply title format transformations from style config
  dynamic_title_has_unit <- FALSE
  if (!is.null(style_config) && !is.null(style_config$title_format)) {
    title_format <- style_config$title_format

    if (title_format$type == "dynamic") {
      if (!is.null(data) && !is.null(title_format$text) && nrow(data) > 0) {
        if (!requireNamespace("glue", quietly = TRUE)) {
          warning("The 'glue' package is required for dynamic titles but is not installed. Using standard title format.")
        } else {
          referenced_cols <- regmatches(
            title_format$text,
            gregexpr("\\{([^}]+)\\}", title_format$text)
          )

          if (length(referenced_cols) > 0 && length(referenced_cols[[1]]) > 0) {
            referenced_cols <- gsub("\\{|\\}", "", referenced_cols[[1]])
            if (any(referenced_cols %in% c("Unit", "unit", "UNIT"))) {
              dynamic_title_has_unit <- TRUE
            }

            missing_cols <- setdiff(referenced_cols, names(data))
            if (length(missing_cols) > 0) {
              warning(paste("Columns referenced in dynamic title template but not found in data:",
                            paste(missing_cols, collapse=", ")))
            } else {
              plot_title <- glue::glue_data(data[1, ], title_format$text)
            }
          } else {
            plot_title <- title_format$text
          }
        }
      }
    }
    else if (title_format$type == "full") {
      plot_title <- title_format$text
    }
    else if (title_format$type == "prefix") {
      separator <- if (!is.null(title_format$sep)) title_format$sep else " "
      plot_title <- paste0(title_format$text, separator, plot_title)
    }
    else if (title_format$type == "suffix") {
      separator <- if (!is.null(title_format$sep)) title_format$sep else " "
      plot_title <- paste0(plot_title, separator, title_format$text)
    }
  }

  # Add unit to title if appropriate
  if (!is.null(style_config) &&
      (!is.null(style_config$title_format) &&
       (style_config$title_format$type != "dynamic" ||
        (style_config$title_format$type == "dynamic" && !dynamic_title_has_unit))) &&
      style_config$add_unit_to_title && !is.null(unit_name)) {
    if (tolower(unit_name) == "percent") {
      plot_title <- paste0(plot_title, " (%)")
    } else {
      plot_title <- paste0(plot_title, " (", unit_name, ")")
    }
  }

  # Store the base title before adding panel value
  base_title <- plot_title

  # For plot display title - check if panel_val is unique in dataset
  panel_is_unique <- FALSE
  if (!separate_figure && !is.null(data) && !is.null(panel_val)) {
    if (!is.null(data) && panel_var %in% names(data)) {
      unique_panel_vals <- unique(data[[panel_var]])
      panel_is_unique <- length(unique_panel_vals) == 1
    }
  }

  # For the display title, only add panel value if it's not a unique value
  # or if separate_figure is TRUE
  if (separate_figure && !is.null(panel_val)) {
    plot_title <- paste0(base_title, " - ", panel_val)
  } else if (!panel_is_unique && !is.null(panel_val)) {
    plot_title <- paste0(base_title, " - ", panel_val)
  }

  # Create filename - this will be different from the plot title
  # For filename, always include the panel value even if it's unique
  if (!is.null(panel_val)) {
    export_name <- paste0(base_title, " - ", panel_val)
  } else {
    export_name <- base_title
  }

  # Format the unit in the filename differently from the plot title
  if (!is.null(unit_name)) {
    # For percent/percentage units, use (%) in the filename just like in the title
    if (grepl("percent", tolower(unit_name))) {
      export_name <- gsub("\\s*\\([^)]*\\)", " (%)", export_name)
    }
    # For other units, remove spaces in the unit name for the filename
    else if (grepl(" ", unit_name)) {
      # First, extract the unit part
      unit_pattern <- paste0("\\(", unit_name, "\\)")
      compact_unit <- gsub(" ", "", unit_name)
      export_name <- gsub(unit_pattern, paste0("(", compact_unit, ")"), export_name)
    }
  }

  # Make export name file-safe but preserve special characters (like %)
  # Clean but keep % signs
  export_name <- gsub("[^a-zA-Z0-9_\\-\\. ()%]", "-", export_name)

  # Replace multiple spaces with a single space
  export_name <- gsub("\\s+", " ", export_name)

  # Trim any leading/trailing whitespace
  export_name <- trimws(export_name)

  # Add plot type suffix if needed
  if (!is.null(plot_type)) {
    if (plot_type == "stack") {
      export_name <- paste(export_name, "stack")
    } else if (plot_type == "unstack") {
      export_name <- paste(export_name, "unstack")
    }
  }

  # Make sure the name isn't too long for a filename
  if (nchar(export_name) > 200) {
    export_name <- paste0(substr(export_name, 1, 197), "...")
  }

  return(list(
    title = plot_title,     # For plot display
    export_name = export_name  # For file export
  ))
}

# Ge Plot Styles Help -----------------------------------------------------
#' @title Get Export Configuration Options
#'
#' @description
#' Returns documentation and default values for export configuration options used in plotting functions.
#'
#' @keywords internal
#' @noRd
#'
.get_export_config <- function() {
  # Export config parameters with default values
  export_config_params <- list(
    file_name = "gtap_plots",
    width = NULL,
    height = NULL,
    dpi = 300,
    bg = "white",
    limitsize = FALSE
  )

  # Build message string
  msg <- "my_export_config <- list(\n"

  # Print file_name
  msg <- paste0(msg, "  file_name = \"", export_config_params$file_name, "\",\n")

  # Print width
  msg <- paste0(msg, "  width = ", if(is.null(export_config_params$width)) "NULL" else export_config_params$width, ",\n")

  # Print height
  msg <- paste0(msg, "  height = ", if(is.null(export_config_params$height)) "NULL" else export_config_params$height, ",\n")

  # Print dpi
  msg <- paste0(msg, "  dpi = ", export_config_params$dpi, ",\n")

  # Print bg
  msg <- paste0(msg, "  bg = \"", export_config_params$bg, "\",\n")

  # Print limitsize (last item, no comma)
  msg <- paste0(msg, "  limitsize = ", ifelse(export_config_params$limitsize, "TRUE", "FALSE"), "\n")

  msg <- paste0(msg, ")\n\n")
  msg <- paste0(msg, "# Example usage:\n")
  msg <- paste0(msg, "# comparison_plot(data, x_axis_from = \"REG\", export_config = my_export_config)\n")

  # Output the message
  message(msg)

  return(invisible(export_config_params))
}

#' @title Get Plot Style Configuration
#'
#' @description
#' Returns configuration settings for plot styles, with options to view as a structured dataframe
#' or to look up specific parameters. Also provides parameter validation for custom configurations.
#'
#' @param plot_type Character. Type of plot: "default" (default).
#' @param validate_custom List or NULL. Custom configuration settings to validate.
#'
#' @keywords internal
#' @noRd
.get_plot_style_config <- function(plot_type = "default",
                                   validate_custom = NULL) {
  config <- .calculate_plot_style_config(NULL, plot_type)

  # Start building the message string
  msg <- "my_style_config <- list(\n"

  # Title settings
  msg <- paste0(msg, "\n  # Title settings\n")
  msg <- paste0(msg, "  show_title = ", ifelse(config$show_title, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  title_face = \"", config$title_face, "\",\n")
  msg <- paste0(msg, "  title_size = ", config$title_size, ",\n")
  msg <- paste0(msg, "  title_hjust = ", config$title_hjust, ",\n")
  msg <- paste0(msg, "  add_unit_to_title = ", ifelse(config$add_unit_to_title, "TRUE", "FALSE"), ",\n")

  # Format margin objects as simple vectors with description
  margin_values <- as.numeric(config$title_margin)
  msg <- paste0(msg, "  title_margin = c(", margin_values[1], ", ", margin_values[2],
                ", ", margin_values[3], ", ", margin_values[4], "), #c(top, right, bottom, left)\n")

  # Format title_format as a properly structured list
  tf <- config$title_format
  msg <- paste0(msg, "  title_format = create_title_format(\n")
  msg <- paste0(msg, "    type = \"", .coalesce(tf$type, "standard"), "\", #option: prefix, suffix, full, dynamic\n")
  msg <- paste0(msg, "    text = \"", .coalesce(tf$text, ""), "\",\n")
  msg <- paste0(msg, "    sep = \"", .coalesce(tf$sep, ""), "\"\n")
  msg <- paste0(msg, "  ),\n")

  # X-Axis settings
  msg <- paste0(msg, "\n  # X-Axis settings\n")
  msg <- paste0(msg, "  show_x_axis_title = ", ifelse(config$show_x_axis_title, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  x_axis_title_face = \"", config$x_axis_title_face, "\",\n")
  msg <- paste0(msg, "  x_axis_title_size = ", config$x_axis_title_size, ",\n")

  margin_values <- as.numeric(config$x_axis_title_margin)
  msg <- paste0(msg, "  x_axis_title_margin = c(", margin_values[1], ", ", margin_values[2],
                ", ", margin_values[3], ", ", margin_values[4], "), #c(top, right, bottom, left)\n")

  msg <- paste0(msg, "  show_x_axis_labels = ", ifelse(config$show_x_axis_labels, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  x_axis_text_face = \"", config$x_axis_text_face, "\",\n")
  msg <- paste0(msg, "  x_axis_text_size = ", config$x_axis_text_size, ",\n")
  msg <- paste0(msg, "  x_axis_text_angle = ", config$x_axis_text_angle, ",\n")
  msg <- paste0(msg, "  x_axis_text_hjust = ", config$x_axis_text_hjust, ",\n")
  msg <- paste0(msg, "  x_axis_description = \"", config$x_axis_description, "\",\n")

  # Y-Axis settings
  msg <- paste0(msg, "\n  # Y-Axis settings\n")
  msg <- paste0(msg, "  show_y_axis_title = ", ifelse(config$show_y_axis_title, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  y_axis_title_face = \"", config$y_axis_title_face, "\",\n")
  msg <- paste0(msg, "  y_axis_title_size = ", config$y_axis_title_size, ",\n")

  margin_values <- as.numeric(config$y_axis_title_margin)
  msg <- paste0(msg, "  y_axis_title_margin = c(", margin_values[1], ", ", margin_values[2],
                ", ", margin_values[3], ", ", margin_values[4], "), #c(top, right, bottom, left)\n")

  msg <- paste0(msg, "  show_y_axis_labels = ", ifelse(config$show_y_axis_labels, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  y_axis_text_face = \"", config$y_axis_text_face, "\",\n")
  msg <- paste0(msg, "  y_axis_text_size = ", config$y_axis_text_size, ",\n")
  msg <- paste0(msg, "  y_axis_text_angle = ", config$y_axis_text_angle, ",\n")
  msg <- paste0(msg, "  y_axis_text_hjust = ", config$y_axis_text_hjust, ",\n")
  msg <- paste0(msg, "  y_axis_description = \"", config$y_axis_description, "\",\n")
  msg <- paste0(msg, "  show_axis_titles_on_all_facets = ", ifelse(config$show_axis_titles_on_all_facets, "TRUE", "FALSE"), ",\n")

  # Value Labels
  msg <- paste0(msg, "\n  # Value Labels\n")
  msg <- paste0(msg, "  show_value_labels = ", ifelse(config$show_value_labels, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  value_label_face = \"", config$value_label_face, "\",\n")
  msg <- paste0(msg, "  value_label_size = ", config$value_label_size, ",\n")
  msg <- paste0(msg, "  value_label_position = \"", config$value_label_position, "\",\n")
  msg <- paste0(msg, "  value_label_decimal_places = ", config$value_label_decimal_places, ",\n")

  # Legend
  msg <- paste0(msg, "\n  # Legend\n")
  msg <- paste0(msg, "  show_legend = ", ifelse(config$show_legend, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  show_legend_title = ", ifelse(config$show_legend_title, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  legend_position = \"", config$legend_position, "\",\n")
  msg <- paste0(msg, "  legend_title_face = \"", config$legend_title_face, "\",\n")
  msg <- paste0(msg, "  legend_text_face = \"", config$legend_text_face, "\",\n")
  msg <- paste0(msg, "  legend_text_size = ", config$legend_text_size, ",\n")

  # Panel Strip
  msg <- paste0(msg, "\n  # Panel Strip\n")
  msg <- paste0(msg, "  strip_face = \"", config$strip_face, "\",\n")
  msg <- paste0(msg, "  strip_text_size = ", config$strip_text_size, ",\n")
  msg <- paste0(msg, "  strip_background = \"", config$strip_background, "\",\n")

  margin_values <- as.numeric(config$strip_text_margin)
  msg <- paste0(msg, "  strip_text_margin = c(", margin_values[1], ", ", margin_values[2],
                ", ", margin_values[3], ", ", margin_values[4], "), #c(top, right, bottom, left)\n")

  # Panel Layout
  msg <- paste0(msg, "\n  # Panel Layout\n")
  msg <- paste0(msg, "  panel_spacing = ", config$panel_spacing, ",\n")
  msg <- paste0(msg, "  panel_rows = ", if(is.null(config$panel_rows)) "NULL" else config$panel_rows, ",\n")
  msg <- paste0(msg, "  panel_cols = ", if(is.null(config$panel_cols)) "NULL" else config$panel_cols, ",\n")
  msg <- paste0(msg, "  theme = ", if(is.null(config$theme)) "NULL" else "custom_theme", ",\n")

  # Color
  msg <- paste0(msg, "\n  # Colors and Grid \n")
  msg <- paste0(msg, "  color_tone = ", if(is.null(config$color_tone)) "NULL" else paste0("\"", config$color_tone, "\""), ",\n")
  msg <- paste0(msg, "  color_palette_type = \"", config$color_palette_type, "\", #option: qualitative, sequential, diverging\n")
  msg <- paste0(msg, "  positive_color = \"", config$positive_color, "\",\n")
  msg <- paste0(msg, "  negative_color = \"", config$negative_color, "\",\n")
  msg <- paste0(msg, "  background_color = \"", config$background_color, "\",\n")
  msg <- paste0(msg, "  grid_color = \"", config$grid_color, "\",\n")
  msg <- paste0(msg, "  show_grid_major_x = ", ifelse(config$show_grid_major_x, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  show_grid_major_y = ", ifelse(config$show_grid_major_y, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  show_grid_minor_x = ", ifelse(config$show_grid_minor_x, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  show_grid_minor_y = ", ifelse(config$show_grid_minor_y, "TRUE", "FALSE"), ",\n")

  # Zero Line
  msg <- paste0(msg, "\n  # Zero Line\n")
  msg <- paste0(msg, "  show_zero_line = ", ifelse(config$show_zero_line, "TRUE", "FALSE"), ",\n")
  msg <- paste0(msg, "  zero_line_type = \"", config$zero_line_type, "\",\n")
  msg <- paste0(msg, "  zero_line_color = \"", config$zero_line_color, "\",\n")
  msg <- paste0(msg, "  zero_line_size = ", config$zero_line_size, ",\n")
  msg <- paste0(msg, "  zero_line_position = ", config$zero_line_position, ",\n")

  # Bar Chart
  msg <- paste0(msg, "\n  # Bar Chart\n")
  msg <- paste0(msg, "  bar_width = ", config$bar_width, ",\n")
  msg <- paste0(msg, "  bar_spacing = ", config$bar_spacing, ",\n")

  # Scale Settings
  msg <- paste0(msg, "\n  # Scale Settings\n")
  if (is.null(config$scale_limit)) {
    msg <- paste0(msg, "  scale_limit = NULL,\n")
  } else {
    msg <- paste0(msg, "  scale_limit = c(", paste(config$scale_limit, collapse=", "), "),\n")
  }
  msg <- paste0(msg, "  scale_increment = ", if(is.null(config$scale_increment)) "NULL" else config$scale_increment, ",\n")

  # Scale Expansion
  msg <- paste0(msg, "\n  # Scale Expansion\n")
  msg <- paste0(msg, "  expansion_y_mult = c(", paste(config$expansion_y_mult, collapse=", "), "),\n")
  msg <- paste0(msg, "  expansion_x_mult = c(", paste(config$expansion_x_mult, collapse=", "), "),\n")

  # Font Size Control
  msg <- paste0(msg, "\n  # Font Size Control\n")
  msg <- paste0(msg, "  all_font_size = ", config$all_font_size, ",\n")

  # Data Sorting
  msg <- paste0(msg, "\n  # Data Sorting\n")
  msg <- paste0(msg, "  sort_data_by_value = ", ifelse(config$sort_data_by_value, "TRUE", "FALSE"), ",\n")

  # Plot Margin Settings
  msg <- paste0(msg, "\n  # Plot Margin\n")
  margin_values <- as.numeric(config$plot.margin)
  msg <- paste0(msg, "  plot.margin = c(", margin_values[1], ", ", margin_values[2],
                ", ", margin_values[3], ", ", margin_values[4], ") #c(top, right, bottom, left)\n")

  msg <- paste0(msg, ")\n\n")
  msg <- paste0(msg, "# Example usage:\n")
  msg <- paste0(msg, "# comparison_plot(data, x_axis_from = \"REG\", plot_style_config = my_style_config)\n")

  # Output the message
  message(msg)

  return(invisible(config))
}

Try the GTAPViz package in your browser

Any scripts or data that you put into this service are public.

GTAPViz documentation built on June 8, 2025, 11:43 a.m.