#' Polar Coordinates with Enhanced Controls
#'
#' An extended version of [`coord_radial()`][ggplot2::coord_radial], providing
#' additional customization options.
#'
#' @inheritParams ggplot2::coord_radial
#' @param thetalim,rlim Limits for the `theta` and `r` axes.
#' @param inner.radius A numeric in `[0, 1)` indicates the inner radius.
#' @param outer.radius A numeric in `(0, 1]` indicates the outer radius.
#' [`coord_radial()`][ggplot2::coord_radial] by default uses `0.8`.
#' @examples
#' ggplot(mtcars, aes(disp, mpg)) +
#'     geom_point() +
#'     coord_circle(
#'         start = -0.4 * pi, end = 0.4 * pi,
#'         inner.radius = 0.3, outer.radius = 1
#'     )
#' ggplot(mtcars, aes(disp, mpg)) +
#'     geom_point() +
#'     coord_circle(
#'         start = -0.4 * pi, end = 0.4 * pi,
#'         inner.radius = 0.3, outer.radius = 0.5
#'     )
#' @importFrom ggplot2 ggproto
#' @importFrom rlang arg_match0
#' @export
coord_circle <- function(theta = "x", start = 0, end = NULL,
                         thetalim = NULL, rlim = NULL, expand = FALSE,
                         direction = 1, clip = "off",
                         r.axis.inside = NULL, rotate.angle = FALSE,
                         inner.radius = 0, outer.radius = 0.95) {
    theta <- arg_match0(theta, c("x", "y"))
    assert_number_decimal(start, allow_infinite = FALSE)
    assert_number_decimal(end, allow_infinite = FALSE, allow_null = TRUE)
    clip <- arg_match0(clip, c("off", "on"))

    valid_inside_axis <- .rlang_check_bool(r.axis.inside,
        allow_null = TRUE
    ) ||
        .rlang_check_number(r.axis.inside,
            allow_decimal = TRUE, allow_infinite = FALSE
        ) == 0L
    if (!valid_inside_axis) {
        cli_abort(
            "{.arg r.axis.inside} must be a single boolean value or a number"
        )
    }

    assert_bool(rotate.angle)
    assert_number_decimal(inner.radius,
        min = 0, max = 1,
        allow_infinite = FALSE
    )
    assert_number_decimal(outer.radius,
        min = 0, max = 1,
        allow_infinite = FALSE
    )
    if (inner.radius >= outer.radius) {
        cli_abort(
            "{.arg outer.radius} must be larger than {.arg inner.radius}"
        )
    }
    r <- if (theta == "x") "y" else "x"

    arc <- c(start, end %||% (start + 2 * pi))
    if (arc[1] > arc[2]) {
        n_rotate <- ((arc[1] - arc[2]) %/% (2 * pi)) + 1
        arc[1] <- arc[1] - n_rotate * 2 * pi
    }
    r.axis.inside <- r.axis.inside %||% !(abs(arc[2] - arc[1]) >= 1.999 * pi)
    inner_radius <- c(inner.radius, outer.radius) / 2

    ggproto(NULL, CoordCircle,
        limits = list(theta = thetalim, r = rlim),
        theta = theta,
        r = r,
        arc = arc,
        expand = expand,
        direction = sign(direction),
        r_axis_inside = r.axis.inside,
        rotate_angle = rotate.angle,
        inner_radius = inner_radius,
        clip = clip
    )
}

circle_panel_params <- function(self, scale_x, scale_y, params = list()) {
    out <- ggproto_parent(ggplot2::CoordRadial, self)$setup_panel_params(
        scale_x, scale_y, params
    )
    out$bbox <- ggfun("polar_bbox")(
        self$arc, margin = c(0, 0, 0, 0),
        inner_radius = self$inner_radius
    )
    out
}

#' @importFrom ggplot2 ggproto ggproto_parent
CoordCircle <- ggproto(
    "CoordCircle", ggplot2::CoordRadial,
    setup_panel_params = circle_panel_params
)
