#' Check Arguments When Supplied
#'
#' These functions check arguments only when they are supplied (`when_supplied()`) or when not `NULL` (`when_not_null()`). Multiple checks can be applied in sequence. This allows arguments not to have to be supplied, but checks them only if they are.
#'
#' @inheritParams arg_is
#' @param \dots `arg_*()` functions or unevaluated function calls to be applied to `x`. See Examples.
#'
#' @details
#' An error will be thrown only if `x` is supplied and fails one of the supplied checks (`when_supplied()`) or is not `NULL` and fails one of the supplied checks (`when_not_null()`).
#'
#' The `...` arguments can be passed either as functions, e.g.,
#' ```
#' when_supplied(x,
#'               arg_number,
#'               arg_gt)
#' ```
#' or as unevaluated function calls with the `x` argument absent, e.g.,
#' ```
#' when_supplied(x,
#'               arg_number(),
#'               arg_gt(bound = 0))
#' ```
#' or as a mixture of both.
#'
#' `when_supplied()` only makes sense to use for an argument that has no default value but which can be omitted. `when_not_null()` makes sense to use for an argument with a default value of `NULL`.
#'
#' @inherit arg_is return
#'
#' @seealso [arg_or()], [arg_supplied()]
#'
#' @examples
#' f <- function(z) {
#'   when_supplied(z,
#'                 arg_number,
#'                 arg_between(c(0, 1)))
#' }
#'
#' try(f())    # No error: not supplied
#' try(f("a")) # Error: not a number
#' try(f(2))   # Error: not within 0-1 range
#' try(f(.7))  # No error: number within range
#'
#' g <- function(z = NULL) {
#'   when_not_null(z,
#'                 arg_number,
#'                 arg_between(c(0, 1)))
#' }
#'
#' try(g())     # No error: NULL okay
#' try(g(NULL)) # No error: NULL okay
#' try(g("a"))  # Error: not a number
#' try(g(2))    # Error: not within 0-1 range
#' try(g(.7))   # No error: number within range

#' @export
when_supplied <- function(x, ..., .arg = rlang::caller_arg(x), .call) {
  if (!rlang::is_missing(x)) {
    force(.arg)
    arg_dots_supplied(..., .call = rlang::current_env()) |>
      internal_arg()

    dots <- rlang::call_match(dots_expand = FALSE)[["..."]]
    x_name <- rlang::caller_arg(x)

    for (i in seq_along(dots)) {
      cnd <- .to_arg_fun_call(dots[[i]], x_name, .arg) |>
        eval.parent() |>
        rlang::catch_cnd()

      if (rlang::cnd_inherits(cnd, "internal_arg_error")) {
        err("", .call = rlang::current_env(), parent = cnd)
      }

      if (inherits(cnd, "error")) {
        .msg <- conditionMessage(cnd) |>
          cli::ansi_strsplit("\n") |>
          unlist(recursive = FALSE)

        .msg[1L] <- paste("When {.arg {(.arg)}} is supplied,", ansi_lower_first(.msg[1L]))

        err(.msg, .call = .call)
      }
    }
  }
}

#' @export
#' @rdname when_supplied
when_not_null <- function(x, ..., .arg = rlang::caller_arg(x), .call) {
  if (is_not_null(x)) {
    force(.arg)
    arg_dots_supplied(..., .call = rlang::current_env()) |>
      internal_arg()

    dots <- rlang::call_match(dots_expand = FALSE)[["..."]]
    x_name <- rlang::caller_arg(x)

    for (i in seq_along(dots)) {
      cnd <- .to_arg_fun_call(dots[[i]], x_name, .arg) |>
        eval.parent() |>
        rlang::catch_cnd()

      if (rlang::cnd_inherits(cnd, "internal_arg_error")) {
        err("", .call = rlang::current_env(), parent = cnd)
      }

      if (inherits(cnd, "error")) {
        .msg <- conditionMessage(cnd) |>
          cli::ansi_strsplit("\n") |>
          unlist(recursive = FALSE)

        .msg[1L] <- paste(cli::format_inline("When {.arg {(.arg)}} is not {.val {list(NULL)}},"),
                          ansi_lower_first(.msg[1L]))

        err(.msg, .call = .call)
      }
    }
  }
}

.to_arg_fun_call <- function(arg_call, x_name, .arg) {
  if (!is.call(arg_call) ||
      any_apply(as.list(arg_call), identical, as.name("::")) ||
      any_apply(as.list(arg_call), identical, as.name(":::"))) {
    arg_call <- rlang::call2(arg_call)
  }

  call_fun <- eval.parent(arg_call[[1L]])

  fmls <- rlang::fn_fmls_names(call_fun)

  if ("x" %in% fmls) {
    arg_call <- rlang::call_modify(arg_call, !!!list(x = str2lang(x_name)))
  }

  if (".arg" %in% fmls) {
    arg_call <- rlang::call_modify(arg_call, !!!list(.arg = .arg))
  }

  if (".call" %in% fmls) {
    arg_call <- rlang::call_modify(arg_call, !!!list(.call = NULL))
  }

  rlang::call_match(call = arg_call, fn = call_fun)
}
