#' Create a remote semantic reviewer
#'
#' `remote_reviewer()` creates a reviewer function backed by an HTTP endpoint.
#' It is intended for teams that already have a policy service, review gateway,
#' or hosted model wrapper that returns llmshieldr-compatible JSON findings.
#'
#' @details
#' The returned function accepts the reviewer prompt generated by llmshieldr and
#' sends a JSON body to `url`. By default, the body is:
#'
#' ```json
#' {"prompt": "..."}
#' ```
#'
#' The endpoint may return either a JSON response body or plain text containing
#' JSON. If the response body is JSON and `response_path` is supplied, the value
#' at that path is extracted before returning to the scanner. Otherwise the
#' response body is returned as text and parsed by llmshieldr's semantic-review
#' JSON extraction logic.
#'
#' This helper requires the optional `httr2` package and performs no network
#' calls until the reviewer function is invoked.
#'
#' @param url Endpoint URL.
#' @param headers Named character vector or list of HTTP headers.
#' @param body_field JSON field name used for the reviewer prompt.
#' @param response_path Optional character vector path to extract from a JSON
#'   response, such as `c("data", "findings")`.
#' @param timeout Request timeout in seconds.
#'
#' @return A reviewer function suitable for `reviewer =` in [scan_prompt()],
#'   [scan_output()], [scan_context()], or [secure_chat()].
#' @examples
#' \dontrun{
#' reviewer <- remote_reviewer(
#'   "https://policy.example.com/review",
#'   headers = c(Authorization = "Bearer <token>")
#' )
#'
#' scan_prompt("Review me", reviewer = reviewer, checks = "llm")
#' }
#' @export
remote_reviewer <- function(url,
                            headers = list(),
                            body_field = "prompt",
                            response_path = NULL,
                            timeout = 30) {
  .check_httr2()
  .check_string(url, "url")
  .check_string(body_field, "body_field")
  .validate_nullable_limit(timeout, "timeout", allow_null = FALSE)
  if (!is.null(response_path) && !is.character(response_path)) {
    cli::cli_abort("{.arg response_path} must be a character vector or {.code NULL}.")
  }
  if (!is.list(headers) && !is.character(headers)) {
    cli::cli_abort("{.arg headers} must be a named list, named character vector, or empty list.")
  }
  if (length(headers) > 0L && is.null(names(headers))) {
    cli::cli_abort("{.arg headers} must be named.")
  }

  force(url)
  force(headers)
  force(body_field)
  force(response_path)
  force(timeout)

  function(prompt) {
    .check_string(prompt, "prompt", allow_empty = TRUE)
    body <- stats::setNames(list(prompt), body_field)
    req <- httr2::request(url)
    if (length(headers) > 0L) {
      header_args <- c(list(req), as.list(as.character(headers)))
      names(header_args) <- c("", names(headers))
      req <- do.call(httr2::req_headers, header_args)
    }
    req <- httr2::req_body_json(req, body)
    req <- httr2::req_timeout(req, timeout)
    resp <- httr2::req_perform(req)
    parsed <- tryCatch(httr2::resp_body_json(resp, simplifyVector = FALSE), error = function(e) NULL)
    if (!is.null(parsed)) {
      value <- .pluck_path(parsed, response_path)
      return(jsonlite::toJSON(value, auto_unbox = TRUE, null = "null"))
    }
    httr2::resp_body_string(resp)
  }
}

.pluck_path <- function(x, path) {
  if (is.null(path) || length(path) == 0L) {
    return(x)
  }
  current <- x
  for (key in path) {
    current <- current[[key]]
    if (is.null(current)) {
      return(NULL)
    }
  }
  current
}

.check_httr2 <- function() {
  rlang::check_installed("httr2",
    reason = "for HTTP-based llmshieldr integrations"
  )
}
