#' Atomic Radar plot
#'
#' @inheritParams common_args
#' @param x A character string of the column name to plot on the x-axis/circles.
#'  A character/factor column is expected.
#' @param x_sep A character string to concatenate the columns in `x`, if multiple columns are provided.
#' @param group_by A character string of the column name(s) to group the data (the lines) by.
#'  Character/factor column(s) is expected.
#' @param groups A character vector of group values (in the `group_by` column) to include in the plot. If NULL, all groups will be included.
#' This can be used to exclude certain groups from the plot or to specify the order of groups in the legend.
#' Only applicable when `group_by` is provided.
#' And this implies `keep_empty` for `group_by` is FALSE, which means the groups not in the data will not be shown in the legend.
#' @param group_by_sep A character string to concatenate the columns in `group_by`, if multiple columns are provided.
#' @param y A character string of the column name to plot on the y-axis.
#'  A numeric column is expected.
#'  If NULL, the count of the x-axis column in each group will be used.
#' @param group_name A character string to name the legend of group.
#' @param scale_y How should the y-axis be scaled? Default is "group".
#'  Other options are "global", "x" and "none".
#'  * If "group", the y-axis will be scaled to the fraction within each group.
#'  * If "global", the y-axis will be scaled to the fraction of the total.
#'  * If "x", the y-axis will be scaled to the fraction of the total within each x-axis group.
#'  * If "none", the y-axis will be scaled to the count of each x-axis group.
#' @param y_min A numeric value to set the minimum value of the y-axis.
#' @param y_max A numeric value to set the maximum value of the y-axis.
#' @param y_nbreaks A numeric value to set the number of breaks in the y-axis.
#' @param polygon A logical value to draw the polygons instead of the circles as panel grid.
#' @param bg_color A character string to set the background color of the plot.
#' @param bg_alpha A numeric value to set the transparency of the background color.
#' @param fill A logical value to fill the polygons with colors.
#' @param linewidth A numeric value to set the width of the lines.
#' @param pt_size A numeric value to set the size of the points.
#' @param max_charwidth A numeric value to set the maximum character width for the x labels.
#' @return A ggplot object
#' @keywords internal
#' @importFrom rlang syms
#' @importFrom dplyr summarise n %>% distinct ungroup
#' @importFrom tidyr complete
#' @importFrom ggplot2 geom_polygon geom_point geom_text scale_y_continuous scale_x_discrete scale_fill_manual
#' @importFrom ggplot2 scale_color_manual coord_polar labs theme element_blank element_line element_text element_rect
#' @importFrom ggplot2 ggproto CoordPolar waiver
RadarPlotAtomic <- function(
    data, x, x_sep = "_", group_by = NULL, group_by_sep = "_", y = NULL, group_name = NULL, groups = NULL,
    scale_y = c("group", "global", "x", "none"), y_min = 0, y_max = NULL, y_nbreaks = 4,
    polygon = FALSE, fill = TRUE, linewidth = 1, pt_size = 4, max_charwidth = 16, bg_color = "grey80", bg_alpha = 0.1,
    theme = "theme_this", theme_args = list(), palette = "Paired", palcolor = NULL,
    facet_by = NULL, facet_scales = "fixed", facet_ncol = NULL, facet_nrow = NULL, facet_byrow = TRUE,
    alpha = 0.2, aspect.ratio = 1, legend.position = waiver(), legend.direction = "vertical",
    keep_na = FALSE, keep_empty = FALSE, title = NULL, subtitle = NULL, ...) {
    ggplot <- if (getOption("plotthis.gglogger.enabled", FALSE)) {
        gglogger::ggplot
    } else {
        ggplot2::ggplot
    }
    x <- check_columns(data, x, force_factor = TRUE, allow_multi = TRUE, concat_multi = TRUE, concat_sep = x_sep)
    y <- check_columns(data, y)
    group_by <- check_columns(data, group_by, force_factor = TRUE, allow_multi = TRUE, concat_multi = TRUE, concat_sep = group_by_sep)
    facet_by <- check_columns(data, facet_by, force_factor = TRUE, allow_multi = TRUE)

    if (is.null(group_by)) {
        data$.group <- factor("")
        group_by <- ".group"
        legend.position <- ifelse(inherits(legend.position, "waiver"), "none", "right")
    } else {
        if (!is.null(groups)) {
            data <- data[data[[group_by]] %in% groups, , drop = FALSE]
            data[[group_by]] <- droplevels(data[[group_by]])
        }
        legend.position <- ifelse(inherits(legend.position, "waiver"), "right", legend.position)
    }

    orig_data <- data
    if (is.null(y)) {
        y <- ".count"
        data <- data %>%
            dplyr::group_by(!!!syms(unique(c(x, group_by, facet_by)))) %>%
            summarise(.count = n(), .groups = "drop")

        for (col in unique(c(x, group_by, facet_by))) {
            data[[col]] <- factor(data[[col]], levels = levels(orig_data[[col]]))
        }
    }


    scale_y <- match.arg(scale_y)
    if (scale_y == "group") {
        data <- data %>%
            dplyr::group_by(!!!syms(unique(c(group_by, facet_by)))) %>%
            mutate(!!sym(y) := !!sym(y) / sum(!!sym(y))) %>%
            ungroup()
        for (col in unique(c(group_by, facet_by))) {
            data[[col]] <- factor(data[[col]], levels = levels(orig_data[[col]]))
        }
    } else if (scale_y == "global") {
        if (!is.null(facet_by)) {
            data <- data %>%
                dplyr::group_by(!!!syms(unique(facet_by))) %>%
                mutate(!!sym(y) := !!sym(y) / sum(!!sym(y))) %>%
                ungroup()
            for (col in unique(facet_by)) {
                data[[col]] <- factor(data[[col]], levels = levels(orig_data[[col]]))
            }
        } else {
            data <- data %>%
                mutate(!!sym(y) := !!sym(y) / sum(!!sym(y)))
        }
    } else if (scale_y == "x") {
        data <- data %>%
            dplyr::group_by(!!!syms(unique(c(x, facet_by)))) %>%
            mutate(!!sym(y) := !!sym(y) / sum(!!sym(y))) %>%
            ungroup()
        for (col in unique(c(x, facet_by))) {
            data[[col]] <- factor(data[[col]], levels = levels(orig_data[[col]]))
        }
    }
    rm(orig_data)

    data <- process_keep_na_empty(data, keep_na, keep_empty)
    keep_empty_x <- keep_empty[[x]]
    keep_empty_group <- if (!is.null(group_by)) keep_empty[[group_by]] else NULL
    keep_empty_facet <- if (!is.null(facet_by)) keep_empty[[facet_by[1]]] else NULL
    if (length(facet_by) > 1) {
        stopifnot("[RadarPLot/SpiderPlot] `keep_empty` for `facet_by` variables must be identical." =
            identical(keep_empty_facet, keep_empty[[facet_by[2]]]))
    }

    # We need all levels of x in each group and facet
    # fill the y value with 0 if the level is missing
    # complete_fill = list(0)
    # names(complete_fill) = y
    # data <- data %>%
    #     dplyr::group_by(!!!syms(unique(c(group_by, facet_by)))) %>%
    #     complete(!!sym(x), fill = complete_fill) %>%
    #     ungroup()

    coord_radar <- function(theta = "x", start = 0, direction = 1) {
        theta <- match.arg(theta, c("x", "y"))
        r <- if (theta == "x") "y" else "x"
        ggproto(
            "CoordRadar", CoordPolar,
            theta = theta, r = r, start = start, clip = "off",
            direction = sign(direction),
            is_linear = function(coord) TRUE
        )
    }

    y_min <- y_min %||% min(data[[y]])
    y_max <- y_max %||% max(data[[y]])
    breaks <- seq(y_min, y_max, length.out = y_nbreaks)
    group_vals <- levels(data[[group_by]])
    if (anyNA(data[[group_by]])) {
        group_vals <- c(group_vals, NA)
    }
    group_colors <- palette_this(group_vals, palette = palette, palcolor = palcolor, NA_keep = TRUE)

    # Add background (circle or polygon based on polygon parameter)
    x_levels <- levels(data[[x]])
    if (anyNA(data[[x]])) {
        # Otherwise there will be an angle shift if NA is present
        x_levels <- c(x_levels, "NA")
        levels(data[[x]]) <- x_levels
        data[[x]][is.na(data[[x]])] <- "NA"
    }
    n_x <- length(x_levels)
    p <- ggplot(data, aes(x = !!sym(x), y = !!sym(y), group = !!sym(group_by), color = !!sym(group_by)))

    if (polygon) {
        # Polygonal background - connects discrete x-axis points
        bg_df <- data.frame(
            x = factor(c(x_levels, x_levels[1], rev(x_levels), rev(x_levels)[1]), levels = x_levels),
            y = c(rep(y_max, n_x), y_max, rep(y_min, n_x), y_min),
            bg_group = 1
        )

        p <- p + geom_polygon(
            data = bg_df,
            aes(x = !!sym("x"), y = !!sym("y"), group = !!sym("bg_group")),
            fill = bg_color, alpha = bg_alpha, color = NA, inherit.aes = FALSE
        )
    } else {
        # Circular background - smooth interpolated circle
        # In polar coordinates with discrete x, we need to go from 0.5 to n_x+0.5
        n_smooth <- 360
        x_positions <- seq(0.5, n_x + 0.5, length.out = n_smooth + 1)

        bg_df <- data.frame(
            x_pos = c(x_positions, rev(x_positions)),
            y = c(rep(y_max, n_smooth + 1), rep(y_min, n_smooth + 1)),
            bg_group = 1
        )

        p <- p + geom_polygon(
            data = bg_df,
            aes(x = !!sym("x_pos"), y = !!sym("y"), group = !!sym("bg_group")),
            fill = bg_color, alpha = bg_alpha, color = NA, inherit.aes = FALSE
        )
    }

    # Add radial grid lines that only extend from y_min to y_max
    if (!is.null(facet_by)) {
        grid_df <- data %>% select(!!!syms(facet_by)) %>% distinct()
        grid_df <- expand_grid(grid_df, x = x_levels)
    } else {
        grid_df <- data.frame(x = x_levels)
    }
    # Create two points for each radial line
    n_lines <- nrow(grid_df)
    grid_df <- grid_df[rep(seq_len(n_lines), each = 2), , drop = FALSE]
    grid_df$y <- rep(c(y_min, y_max), times = n_lines)
    # Create a unique identifier for grouping each radial line
    grid_df$line_id <- rep(seq_len(n_lines), each = 2)

    p <- p + geom_path(
        data = grid_df,
        aes(x = !!sym("x"), y = !!sym("y"), group = !!sym("line_id")),
        colour = "grey80", linetype = 1, inherit.aes = FALSE
    )
    if (isTRUE(polygon)) {
        # Create proper polygon grid at each break level
        for (i in seq_along(breaks)) {
            if (!is.null(facet_by)) {
                poly_grid_df <- data %>% select(!!!syms(facet_by)) %>% distinct()
                poly_grid_df <- expand_grid(poly_grid_df, x = x_levels)
            } else {
                poly_grid_df <- data.frame(x = x_levels)
            }
            poly_grid_df$y <- breaks[i]
            poly_grid_df$poly_id <- i

            p <- p + geom_polygon(
                data = poly_grid_df,
                aes(x = !!sym("x"), y = !!sym("y"), group = !!sym("poly_id")),
                fill = "transparent", color = "grey80", linetype = 2, inherit.aes = FALSE
            )
        }
    }

    if (isTRUE(fill)) {
        p <- p + geom_polygon(aes(fill = !!sym(group_by)), alpha = alpha, linewidth = linewidth, show.legend = TRUE)
    } else {
        p <- p + geom_polygon(alpha = alpha, linewidth = linewidth, fill = "transparent")
    }

    if (!is.null(facet_by)) {
        labels_df <- data %>% select(!!!syms(facet_by)) %>% distinct()
        labels_df <- expand_grid(labels_df, breaks)
    } else {
        labels_df <- data.frame(breaks = breaks)
    }

    mc <- min(max(nchar(levels(data[[x]]))), max_charwidth)
    panel.spacing <- theme_args$panel.spacing %||% unit(mc * 2, "points")
    theme_args$panel.spacing <- NULL
    p <- p +
        geom_point(aes(colour = !!sym(group_by), fill = !!sym(group_by)), size = pt_size, shape = 21, show.legend = TRUE) +
        geom_text(
            data = labels_df,
            mapping = aes(y = !!sym("breaks"),
                label = if (scale_y == "none") scales::number(!!sym("breaks")) else scales::percent(!!sym("breaks"))),
            x = pi / (length(breaks) - 0.88), color = "grey20", inherit.aes = FALSE) +
        scale_y_continuous(limits = c(y_min, y_max), breaks = breaks, expand = c(.1, 0, 0, 0)) +
        scale_x_discrete(labels = scales::label_wrap(max_charwidth), drop = !isTRUE(keep_empty_x)) +
        # scale_fill_manual(
        #     name = group_name %||% group_by,
        #     values = palette_this(levels(data[[group_by]]), palette = palette, palcolor = palcolor)
        # ) +
        # scale_color_manual(
        #     values = palette_this(levels(data[[group_by]]), palette = palette, palcolor = palcolor)
        # ) +
        coord_radar(start = -pi / nlevels(data[[x]])) +
        labs(title = title, subtitle = subtitle) +
        do.call(theme, theme_args) +
        ggplot2::theme(
            axis.text.y = element_blank(),
            axis.ticks = element_blank(),
            axis.title = element_blank(),
            panel.grid = element_blank(),
            panel.border = element_blank(),
            panel.spacing = panel.spacing
        )

    if (isTRUE(keep_empty_group)) {
        p <- p + scale_fill_manual(
            name = group_name %||% group_by, values = group_colors, na.value = group_colors["NA"] %||% "grey80",
            breaks = group_vals, limits = group_vals, drop = FALSE
        ) + scale_color_manual(
            values = group_colors, na.value = group_colors["NA"] %||% "grey80",
            breaks = group_vals, limits = group_vals, drop = FALSE
        )
    } else {
        p <- p + scale_fill_manual(
            name = group_name %||% group_by, values = group_colors
        ) + scale_color_manual(
            values = group_colors
        )
    }

    if (isFALSE(polygon)) {
        p <- p + ggplot2::theme(panel.grid.major.y = element_line(
            colour = c(rep("grey80", length(breaks)), NA), linetype = 2))
    }

    dims <- calculate_plot_dimensions(
        base_height = 4.5,
        aspect.ratio = aspect.ratio,
        legend.position = legend.position,
        legend.direction = legend.direction,
        legend_n = length(group_vals),
        legend_nchar = max(nchar(as.character(group_vals)), na.rm = TRUE)
    )

    attr(p, "height") <- dims$height
    attr(p, "width") <- dims$width

    facet_plot(p, facet_by, facet_scales, facet_nrow, facet_ncol, facet_byrow,
        legend.position = legend.position, legend.direction = legend.direction,
        drop = !isTRUE(keep_empty_facet))
}

#' Radar plot / Spider plot
#'
#' @description Create a radar plot or spider plot for a series of data.
#'   Radar plot uses circles as the plot grid and Spider plot uses polygons.
#' @rdname radarplot
#' @inheritParams common_args
#' @inheritParams RadarPlotAtomic
#' @importFrom ggplot2 waiver
#' @return A ggplot object or wrap_plots object or a list of ggplot objects
#' @export
RadarPlot <- function(
    data, x, x_sep = "_", group_by = NULL, group_by_sep = "_", y = NULL, group_name = NULL, groups = NULL,
    scale_y = c("group", "global", "x", "none"), y_min = 0, y_max = NULL, y_nbreaks = 4, bg_color = "grey80", bg_alpha = 0.1,
    fill = TRUE, linewidth = 1, pt_size = 4, max_charwidth = 16, split_by = NULL, split_by_sep = "_",
    theme = "theme_this", theme_args = list(), palette = "Paired", palcolor = NULL,
    facet_by = NULL, facet_scales = "fixed", facet_ncol = NULL, facet_nrow = NULL, facet_byrow = TRUE,
    alpha = 0.2, aspect.ratio = 1, legend.position = waiver(), legend.direction = "vertical",
    keep_na = FALSE, keep_empty = FALSE, title = NULL, subtitle = NULL, seed = 8525, combine = TRUE, nrow = NULL, ncol = NULL, byrow = TRUE,
    axes = NULL, axis_titles = axes, guides = NULL, design = NULL, ...) {

    validate_common_args(seed, facet_by = facet_by)
    keep_na <- check_keep_na(keep_na, c(x, group_by, facet_by, split_by))
    keep_empty <- check_keep_empty(keep_empty, c(x, group_by, facet_by, split_by))
    theme <- process_theme(theme)
    split_by <- check_columns(data, split_by, force_factor = TRUE, allow_multi = TRUE, concat_multi = TRUE, concat_sep = split_by_sep)

    if (!is.null(split_by)) {
        data <- process_keep_na_empty(data, keep_na, keep_empty, col = split_by)
        keep_na[[split_by]] <- NULL
        keep_empty[[split_by]] <- NULL
        datas <- split(data, data[[split_by]])
        # keep the order of levels
        datas <- datas[levels(data[[split_by]])]
    } else {
        datas <- list(data)
        names(datas) <- "..."
    }
    palette <- check_palette(palette, names(datas))
    palcolor <- check_palcolor(palcolor, names(datas))
    legend.direction <- check_legend(legend.direction, names(datas), "legend.direction")
    legend.position <- check_legend(legend.position, names(datas), "legend.position")

    plots <- lapply(
        names(datas), function(nm) {
            default_title <- if (length(datas) == 1 && identical(nm, "...")) NULL else nm
            if (is.function(title)) {
                title <- title(default_title)
            } else {
                title <- title %||% default_title
            }
            RadarPlotAtomic(datas[[nm]],
                x = x, x_sep = x_sep, group_by = group_by, group_by_sep = group_by_sep, y = y, group_name = group_name, groups = groups,
                scale_y = scale_y, y_min = y_min, y_max = y_max, y_nbreaks = y_nbreaks, polygon = FALSE,
                fill = fill, linewidth = linewidth, pt_size = pt_size, max_charwidth = max_charwidth, bg_color = bg_color, bg_alpha = bg_alpha,
                theme = theme, theme_args = theme_args, palette = palette[[nm]], palcolor = palcolor[[nm]],
                facet_by = facet_by, facet_scales = facet_scales, facet_ncol = facet_ncol, facet_nrow = facet_nrow, facet_byrow = facet_byrow,
                alpha = alpha, aspect.ratio = aspect.ratio, legend.position = legend.position[[nm]], legend.direction = legend.direction[[nm]],
                keep_na = keep_na, keep_empty = keep_empty, title = title, subtitle = subtitle, ...
            )
        }
    )

    combine_plots(plots, combine = combine, nrow = nrow, ncol = ncol, byrow = byrow,
        axes = axes, axis_titles = axis_titles, guides = guides, design = design)
}

#' @rdname radarplot
#' @inheritParams RadarPlot
#' @export
#' @importFrom ggplot2 waiver
#' @examples
#' \donttest{
#' set.seed(8525)
#' # use the count
#' data <- data.frame(
#'    x = factor(
#'     c(rep("A", 20), rep("B", 30), rep(NA, 30), rep("D", 40), rep("E", 50)),
#'     levels = LETTERS[1:5]
#'    ),
#'    group = factor(
#'     sample(c("G1", NA, "G3", "G4"), 170, replace = TRUE),
#'     levels = c("G1", "G2", "G3", "G4")
#'    )
#' )
#'
#' RadarPlot(data, x = "x")
#' RadarPlot(data, x = "x", keep_na = TRUE, keep_empty = TRUE)
#' RadarPlot(data, x = "x", bg_color = "lightpink")
#' RadarPlot(data, x = "x", scale_y = "none")
#' RadarPlot(data, x = "x", group_by = "group", keep_na = TRUE)
#' RadarPlot(data, x = "x", facet_by = "group")
#' SpiderPlot(data, x = "x")
#' SpiderPlot(data, x = "x", group_by = "group")
#' # use the y value
#' data <- data.frame(
#'    x = rep(LETTERS[1:5], 2),
#'    y = c(1, 3, 6, 4, 2, 5, 7, 8, 9, 10),
#'    group = rep(c("G1", "G2"), each = 5)
#' )
#' RadarPlot(data, x = "x", y = "y", scale_y = "none", group_by = "group")
#' RadarPlot(data, x = "x", y = "y", facet_by = "group")
#' RadarPlot(data, x = "x", y = "y", split_by = "group")
#' RadarPlot(data, x = "x", y = "y", split_by = "group",
#'           palette = c(G1 = "Set1", G2 = "Paired"))
#' }
SpiderPlot <- function(
    data, x, x_sep = "_", group_by = NULL, group_by_sep = "_", y = NULL, group_name = NULL, groups = NULL,
    scale_y = c("group", "global", "x", "none"), y_min = 0, y_max = NULL, y_nbreaks = 4, bg_color = "grey80", bg_alpha = 0.1,
    fill = TRUE, linewidth = 1, pt_size = 4, max_charwidth = 16, split_by = NULL, split_by_sep = "_",
    theme = "theme_this", theme_args = list(), palette = "Paired", palcolor = NULL,
    facet_by = NULL, facet_scales = "fixed", facet_ncol = NULL, facet_nrow = NULL, facet_byrow = TRUE,
    alpha = 0.2, aspect.ratio = 1, legend.position = waiver(), legend.direction = "vertical",
    keep_na = FALSE, keep_empty = FALSE, title = NULL, subtitle = NULL, seed = 8525, combine = TRUE, nrow = NULL, ncol = NULL, byrow = TRUE,
    axes = NULL, axis_titles = axes, guides = NULL, design = NULL, ...) {

    validate_common_args(seed, facet_by = facet_by)
    keep_na <- check_keep_na(keep_na, c(x, group_by, facet_by, split_by))
    keep_empty <- check_keep_empty(keep_empty, c(x, group_by, facet_by, split_by))
    theme <- process_theme(theme)
    split_by <- check_columns(data, split_by, force_factor = TRUE, allow_multi = TRUE, concat_multi = TRUE, concat_sep = split_by_sep)

    if (!is.null(split_by)) {
        data <- process_keep_na_empty(data, keep_na, keep_empty, col = split_by)
        keep_na[[split_by]] <- NULL
        keep_empty[[split_by]] <- NULL
        datas <- split(data, data[[split_by]])
        # keep the order of levels
        datas <- datas[levels(data[[split_by]])]
    } else {
        datas <- list(data)
        names(datas) <- "..."
    }
    palette <- check_palette(palette, names(datas))
    palcolor <- check_palcolor(palcolor, names(datas))
    legend.direction <- check_legend(legend.direction, names(datas), "legend.direction")
    legend.position <- check_legend(legend.position, names(datas), "legend.position")

    plots <- lapply(
        names(datas), function(nm) {
            default_title <- if (length(datas) == 1 && identical(nm, "...")) NULL else nm
            if (is.function(title)) {
                title <- title(default_title)
            } else {
                title <- title %||% default_title
            }
            RadarPlotAtomic(datas[[nm]],
                x = x, x_sep = x_sep, group_by = group_by, group_by_sep = group_by_sep, y = y, group_name = group_name, groups = groups,
                scale_y = scale_y, y_min = y_min, y_max = y_max, y_nbreaks = y_nbreaks, polygon = TRUE, bg_color = bg_color, bg_alpha = bg_alpha,
                fill = fill, linewidth = linewidth, pt_size = pt_size, max_charwidth = max_charwidth,
                theme = theme, theme_args = theme_args, palette = palette[[nm]], palcolor = palcolor[[nm]],
                facet_by = facet_by, facet_scales = facet_scales, facet_ncol = facet_ncol, facet_nrow = facet_nrow, facet_byrow = facet_byrow,
                alpha = alpha, aspect.ratio = aspect.ratio, legend.position = legend.position[[nm]], legend.direction = legend.direction[[nm]],
                keep_na = keep_na, keep_empty = keep_empty, title = title, subtitle = subtitle, ...
            )
        }
    )

    combine_plots(plots, combine = combine, nrow = nrow, ncol = ncol, byrow = byrow,
        axes = axes, axis_titles = axis_titles, guides = guides, design = design)
}
