#' Generate an Image With DALL-E 3
#'
#' @param prompt The prompt for image generation.
#' @param out_file The path where to save the generated image.
#' @param openai_api_key OpenAI's API key.
#'
#' @importFrom httr add_headers content content_type_json POST stop_for_status
#' @importFrom jsonlite fromJSON toJSON
#' @importFrom utils download.file
#'
#' @export
#'
generate_image <-

function(prompt, out_file = tempfile(fileext = ".png"),
                           openai_api_key = Sys.getenv("OPENAI_API_KEY")) {
  post_res <- POST(
    paste0(api_url, "/images/generations"),
    add_headers("Authorization" = paste("Bearer", openai_api_key)),
    content_type_json(),
    body = toJSON(
      list(model = "dall-e-3", prompt = prompt, n = 1, size = "1024x1024"),
      auto_unbox = TRUE
    )
  )
  stop_for_status(post_res)
  download.file(fromJSON(content(post_res, as = "text", encoding = "UTF-8"))$data$url, out_file)
  return(out_file)
}

#' ChatGPT: List Models
#'
#' @param openai_api_key OpenAI's API key.
#'
#' @examples
#' \dontrun{
#' list_models()
#' }
#'
#' @importFrom httr add_headers content GET stop_for_status
#' @importFrom jsonlite fromJSON
#'
#' @return A data.frame with the available models to be used by OpenAI's API.
#'
#' @export
#'
list_models <- function(openai_api_key = Sys.getenv("OPENAI_API_KEY")) {
  get_res <- GET(
    paste0(api_url, "/models"),
    add_headers("Authorization" = paste("Bearer", openai_api_key))
  )
  stop_for_status(get_res)
  fromJSON(content(get_res, as = "text", encoding = "UTF-8"))$data
}

#' Get GPT Completions Endpoint
#'
#' @param prompt The prompt to generate completions for.
#' @param openai_api_key OpenAI's API key.
#' @param messages Available variable, to send the needed messages list to ChatGPT.
#'
#' @importFrom httr add_headers content content_type_json POST use_proxy
#' @importFrom jsonlite toJSON
#'
gpt_get_completions <- function(prompt, openai_api_key = Sys.getenv("OPENAI_API_KEY"),
                                messages = NULL) {
  if (nchar(openai_api_key) == 0) {
    stop("`OPENAI_API_KEY` not provided.")
  }
  # See https://platform.openai.com/docs/api-reference/chat .
  params <- list(
    model = Sys.getenv("OPENAI_MODEL", "gpt-4o-mini"),
    max_tokens = as.numeric(Sys.getenv("OPENAI_MAX_TOKENS", 256)),
    temperature = as.numeric(Sys.getenv("OPENAI_TEMPERATURE", 1)),
    top_p = as.numeric(Sys.getenv("OPENAI_TOP_P", 1)),
    frequency_penalty = as.numeric(Sys.getenv("OPENAI_FREQUENCY_PENALTY", 0)),
    presence_penalty = as.numeric(Sys.getenv("OPENAI_PRESENCE_PENALTY", 0)),
    logprobs = as.logical(Sys.getenv("OPENAI_LOGPROBS", FALSE))
  )
  if (get_verbosity()) {
    message(paste0("\n*** ChatGPT input:\n\n", prompt, "\n"))
  }
  return_language <- Sys.getenv("OPENAI_RETURN_LANGUAGE")
  if (nchar(return_language) > 0) {
    return_language <- paste0("You return all your replies in ", return_language, ".")
  }
  if (is.null(messages)) {
    messages <- list(
      list(
        role = "system",
        content = paste(
          "You are a helpful assistant with extensive knowledge of R programming.",
          return_language
        )
      ),
      list(role = "user", content = prompt)
    )
  } else {
    # If there are messages provided, then add the `return_language` if available.
    messages[[which(sapply(messages, function(message) message$role == "system"))]]$content <-
      paste(
        messages[[which(sapply(messages, function(message) message$role == "system"))]]$content,
        return_language
      )
  }
  # Get the proxy to use, if provided.
  proxy <- NULL
  if (nchar(Sys.getenv("OPENAI_PROXY")) > 0) {
    proxy <- Sys.getenv("OPENAI_PROXY")
    if (grepl("^(?:\\d{1,3}\\.){3}\\d{1,3}:\\d{2,5}$", proxy)) {
      proxy <- use_proxy(gsub(":.*", "", proxy), as.numeric(gsub(".*:", "", proxy)))
    } else {
      stop("Invalid proxy provided in `OPENAI_PROXY`: ", proxy)
    }
  }
  # Run the API query.
  final_res <- list()
  keep_querying <- TRUE
  while (keep_querying) {
    post_res <- POST(
      paste0(api_url, "/chat/completions"),
      add_headers("Authorization" = paste("Bearer", openai_api_key)),
      content_type_json(),
      body = toJSON(c(params, list(messages = messages)), auto_unbox = TRUE),
      proxy
    )
    if (!post_res$status_code %in% 200:299) {
      stop(content(post_res))
    }
    if (get_verbosity() > 1) {
      # If verbose is over 1, show the ongoing GPT response.
      message(content(post_res, as = "text", encoding = "UTF-8"))
    }
    post_res <- content(post_res)
    final_res <- append(final_res, list(post_res))
    # In the case the finish_reason is the length of the message, then we need to keep querying.
    keep_querying <- all(sapply(post_res$choices, function(x) x$finish_reason == "length"))
    # And update the messages sent to ChatGPT, in order to continue the current session.
    messages <- append(
      append(
        messages,
        list(list(role = "assistant", content = parse_response(list(post_res), verbosity = 0)))
      ),
      list(list(role = "user", content = "continue"))
    )
  }
  final_res
}

#' Ask ChatGPT
#'
#' Note: See also `reset_chat_session`.
#'
#' @param question The question to ask ChatGPT.
#' @param session_id The ID of the session to be used. We can have different conversations by using
#'   different session IDs.
#' @param openai_api_key OpenAI's API key.
#' @param images A list of images to attach to the question. It could be a list of URLs or paths.
#'
#' @examples
#' \dontrun{
#' cat(ask_chatgpt("What do you think about R language?"))
#' }
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
ask_chatgpt <- function(question, session_id = "1", openai_api_key = Sys.getenv("OPENAI_API_KEY"),
                        images = NULL) {
  # Get the existing chat session messages, and add the new message.
  chat_session_messages <- append(get_chat_session(session_id), list(
    list(role = "user", content = build_prompt_content(question, images))
  ))
  # Send the query to ChatGPT.
  chat_gpt_reply <- parse_response(
    gpt_get_completions(question, openai_api_key, chat_session_messages)
  )
  chat_session_messages <- append(chat_session_messages, list(
    list(role = "assistant", content = chat_gpt_reply)
  ))
  # Update the chat session messages with the new question and the reply.
  reset_chat_session(chat_session_messages, session_id)
  chat_gpt_reply
}

#' Run a ChatGPT RStudio Addin
#'
#' @param addin_name The name of the adding to execute.
#'
#' @importFrom rstudioapi as.document_range getActiveDocumentContext modifyRange
#'
run_addin <- function(addin_name) {
  # Select which addin has to be used.
  addin_function <- switch(addin_name,
    "comment_code" = comment_code,
    "complete_code" = complete_code,
    "create_unit_tests" = create_unit_tests,
    "create_variable_name" = create_variable_name,
    "document_code" = document_code,
    "explain_code" = explain_code,
    "find_issues_in_code" = find_issues_in_code,
    "optimize_code" = optimize_code,
    "refactor_code" = refactor_code,
    stop("`addin_name` not found.")
  )
  # Get the selected code.
  doc_context <- getActiveDocumentContext()
  selected_code <- doc_context$selection[[1]]$text
  is_full_file <- all(nchar(selected_code) == 0)
  # If no code is selected, use the whole file.
  if (is_full_file) {
    selected_code <- doc_context$contents
  }
  selected_code <- paste0(selected_code, collapse = "\n")
  # Apply the addin function.
  out <- addin_function(selected_code)
  if (as.logical(Sys.getenv("OPENAI_ADDIN_REPLACE", FALSE))) {
    doc_range <- doc_context$selection[[1]]$range
    if (is_full_file) {
      doc_range <- as.document_range(c(c(0, 0), c(Inf, Inf)))
    }
    modifyRange(doc_range, out, doc_context$id)
  } else if (get_verbosity()) {
    message(paste0("\n*** ChatGPT output:\n\n", out, "\n"))
  } else {
    warning("Please set one of `OPENAI_ADDIN_REPLACE=TRUE` or `OPENAI_VERBOSE=TRUE`")
  }
  invisible(NULL)
}

run_addin_comment_code <- function() run_addin("comment_code")
run_addin_complete_code <- function() run_addin("complete_code")
run_addin_create_unit_tests <- function() run_addin("create_unit_tests")
run_addin_create_variable_name <- function() run_addin("create_variable_name")
run_addin_document_code <- function() run_addin("document_code")
run_addin_explain_code <- function() run_addin("explain_code")
run_addin_find_issues_in_code <- function() run_addin("find_issues_in_code")
run_addin_optimize_code <- function() run_addin("optimize_code")
run_addin_refactor_code <- function() run_addin("refactor_code")

#' Ask ChatGPT
#'
#' Opens an interactive chat session with ChatGPT
#'
#' @importFrom miniUI gadgetTitleBar miniPage
#' @importFrom shiny actionButton br icon observeEvent onStop runGadget stopApp textAreaInput
#' @importFrom shiny updateTextAreaInput wellPanel
#' @importFrom utils getFromNamespace
#'
run_addin_ask_chatgpt <- function() {
  reset_chat_session()
  ui <- miniPage(wellPanel(
    gadgetTitleBar("Ask ChatGPT", NULL),
    textAreaInput("question", "Question:", width = "100%", height = "150px"),
    actionButton("ask_button", "Ask", icon("paper-plane")),
    br(), br(),
    textAreaInput("answer", "Answer:", width = "100%", height = "150px")
  ))
  server <- function(input, output, session) {
    observeEvent(input$ask_button, {
      chatgpt_reply <- ask_chatgpt(input$question)
      if (get_verbosity()) {
        message(paste0("\n*** ChatGPT output:\n\n", chatgpt_reply, "\n"))
      }
      updateTextAreaInput(session, "answer", value = chatgpt_reply)
    })
    observeEvent(input$done, {
      reset_chat_session()
      stopApp()
    })
    onStop(reset_chat_session)
  }
  runGadget(ui, server)
}

#' Build Prompt Content
#'
#' @param question The question to ask ChatGPT.
#' @param images A list of images to attach to the question. It could be a list of URLs or paths.
#'
#' @importFrom xfun base64_encode
#'
build_prompt_content <- function(question, images) {
  if (length(images) == 0) {
    return(question)
  }
  prompt_content <- list(list(type = "text", text = question))
  append(prompt_content, lapply(images, function(image) {
    # If it's a local file, then transform it to base 64 encoding. If not, return the "URL".
    image_url <- image
    if (file.exists(image)) {
      image_url <- paste0("data:image/jpeg;base64,", base64_encode(image))
    }
    list(type = "image_url", image_url = list(url = image_url))
  }))
}

#' Parse OpenAI API Response
#'
#' Takes the raw response from the OpenAI API and extracts the text content from it.
#'
#' @param raw_responses The raw response object returned by the OpenAI API.
#' @param verbosity The verbosity level for this function.
#'
#' @return Returns a character vector containing the text content of the response.
#'
parse_response <- function(raw_responses, verbosity = get_verbosity()) {
  # Parse the message content of the list of raw_responses. Trim those messages, and paste them.
  parsed_response <- paste(trimws(sapply(raw_responses, function(response) {
    sapply(response$choices, function(x) x$message$content)
  })), collapse = "")
  if (verbosity > 2) {
    # If we are in 3-verbose mode, add the raw_responses as an attribute to the return object.
    attr(parsed_response, "raw_responses") <- raw_responses
  }
  parsed_response
}

#' ChatGPT: Create Unit Tests
#'
#' Create `{testthat}` test cases for the code.
#'
#' @param code The code for which to create unit tests by ChatGPT. If not provided, it will use
#'   what's copied on the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(create_unit_tests("squared_numbers <- function(numbers) {\n  numbers ^ 2\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
create_unit_tests <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0(
    "Using testthat 3e, version over 3.0.0, create a full testthat file, with test cases for the ",
    'following R code: "', code, '"'
  )
  parse_response(gpt_get_completions(prompt))
}

#' 'OpenAI's 'ChatGPT' <https://chat.openai.com/> coding assistant for 'RStudio'. A set
#' of functions and 'RStudio' addins that aim to help the R developer in tedious coding tasks.
#'
"_PACKAGE"

.state <- new.env(parent = emptyenv())

# Empty chat session messages at startup.
assign("chat_session_messages", list(), envir = .state)

api_url <- Sys.getenv("OPENAI_API_URL", "https://api.openai.com/v1")

#' Reset Chat Session
#'
#' This function is intended to be used with `ask_chatgpt`. If we are using `ask_chatgpt` to chat with ChatGPT, and
#' we want to start a new conversation, we must call `reset_chat_session`.
#'
#' @param system_role ChatGPT's role as an AI assistant.
#' @param session_id The ID of the session to be used. If `NULL`, this function will have no effect.
#'
#' @export
#'
reset_chat_session <- function(system_role = "You are a helpful assistant.", session_id = "1") {
  if (is.null(session_id)) {
    return()
  }
  if (is.list(system_role)) {
    # If `system_role` is a list, then it is a ChatGPT session object.
    session <- system_role
  } else {
    # Otherwise, it's a string specifying ChatGPT's role.
    session <- list(list(role = "system", content = system_role))
  }
  all_sessions <- get("chat_session_messages", envir = .state)
  all_sessions[[as.character(session_id)]] <- session
  assign("chat_session_messages", all_sessions, envir = .state)
}

#' Get Chat Session
#'
#' @param session_id The ID of the session to be used. If `NULL`, it will return an empty session.
#'
get_chat_session <- function(session_id = "1") {
  default_session <- list(list(role = "system", content = "You are a helpful assistant."))
  if (is.null(session_id)) {
    return(default_session)
  }
  session <- get("chat_session_messages", envir = .state)[[as.character(session_id)]]
  # If the session was not found, then it's a new (default) session.
  if (is.null(session)) {
    session <- default_session
  }
  session
}

#' Get Verbosity Level
#'
get_verbosity <- function() {
  # `OPENAI_VERBOSE` should be one of `numeric` or `FALSE`/`TRUE`. But we'll return it as numeric.
  suppressWarnings(max(
    as.logical(Sys.getenv("OPENAI_VERBOSE", TRUE)),
    as.numeric(Sys.getenv("OPENAI_VERBOSE", TRUE)),
    na.rm = TRUE
  ))
}

#' ChatGPT: Create Variable Name
#'
#' @param code The code for which to give a variable name to its result. If not provided, it will
#'   use what's copied on the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(create_variable_name("sapply(1:10, function(i) i ** 2)"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
create_variable_name <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Give a good variable name to the result of the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Document Code (in roxygen2 format)
#'
#' @param code The code to be documented by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(document_code("square_numbers <- function(numbers) numbers ** 2"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
document_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Document, in roxygen2 format, this R function: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Explain Code
#'
#' @param code The code to be explained by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(explain_code("for (i in 1:10) {\n  print(i ** 2)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
explain_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Explain the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Comment Code
#'
#' @param code The code to be commented by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(comment_code("for (i in 1:10) {\n  print(i ** 2)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
comment_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Add inline comments to the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Optimize Code
#'
#' @param code The code to be optimized by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(optimize_code("i <- 10\nwhile (i > 0) {\n  i <- i - 1\n  print(i)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
optimize_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Optimize the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Refactor Code
#'
#' @param code The code to be refactored by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(refactor_code("i <- 10\nwhile (i > 0) {\n  i <- i - 1\n  print(i)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
refactor_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Refactor the following R code, returning valid R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Find Issues in Code
#'
#' @param code The code to be analyzed by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(find_issues_in_code("i <- 0\nwhile (i < 0) {\n  i <- i - 1\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
find_issues_in_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Find issues or bugs in the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Complete Code
#'
#' @param code The code to be completed by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(complete_code("# A function to square each element of a vector\nsquare_each <- function("))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
complete_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Complete the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

function(prompt, out_file = tempfile(fileext = ".png"),
                           openai_api_key = Sys.getenv("OPENAI_API_KEY")) {}

This code segment functions to: Send a formatted request to an external image generation API using specified parameters, handle the response validation, and retrieve the resulting output file from the service's returned data URL.

```r
[TASK_BEGIN]
function(prompt, out_file = tempfile(fileext = ".png"),
         openai_api_key = Sys.getenv("OPENAI_API_KEY")) {
    if (missing(prompt)) stop("Prompt must be specified")
    if (nchar(openai_api_key) == 0) stop("OPENAI_API_KEY environment variable not set")

    body <- list(
        model = "dall-e-3",
        prompt = prompt,
        size = "1024x1024",
        n = 1
    )

    response <- POST(
        "https://api.openai.com/v1/images/generations",
        add_headers(
            "Authorization" = paste("Bearer", openai_api_key),
            "Content-Type" = "application/json"
        ),
        body = toJSON(body, auto_unbox = TRUE)
    )

    stop_for_status(response)
    response_content <- fromJSON(content(response, "text", encoding = "UTF-8"))
    image_url <- response_content$data$url

    download.file(image_url, out_file, mode = "wb")
    invisible(out_file)
}
[TASK_END]
```


#' Get Chat Session
#'
#' @param session_id The ID of the session to be used. If `NULL`, it will return an empty session.
#'
get_chat_session <-

function(session_id = "1") {
  default_session <- list(list(role = "system", content = "You are a helpful assistant."))
  if (is.null(session_id)) {
    return(default_session)
  }
  session <- get("chat_session_messages", envir = .state)[[as.character(session_id)]]
  if (is.null(session)) {
    session <- default_session
  }
  session
}

#' Ask ChatGPT
#'
#' Note: See also `reset_chat_session`.
#'
#' @param question The question to ask ChatGPT.
#' @param session_id The ID of the session to be used. We can have different conversations by using
#'   different session IDs.
#' @param openai_api_key OpenAI's API key.
#' @param images A list of images to attach to the question. It could be a list of URLs or paths.
#'
#' @examples
#' \dontrun{
#' cat(ask_chatgpt("What do you think about R language?"))
#' }
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
ask_chatgpt <- function(question, session_id = "1", openai_api_key = Sys.getenv("OPENAI_API_KEY"),
                        images = NULL) {
  # Get the existing chat session messages, and add the new message.
  chat_session_messages <- append(get_chat_session(session_id), list(
    list(role = "user", content = build_prompt_content(question, images))
  ))
  # Send the query to ChatGPT.
  chat_gpt_reply <- parse_response(
    gpt_get_completions(question, openai_api_key, chat_session_messages)
  )
  chat_session_messages <- append(chat_session_messages, list(
    list(role = "assistant", content = chat_gpt_reply)
  ))
  # Update the chat session messages with the new question and the reply.
  reset_chat_session(chat_session_messages, session_id)
  chat_gpt_reply
}

#' Run a ChatGPT RStudio Addin
#'
#' @param addin_name The name of the adding to execute.
#'
#' @importFrom rstudioapi as.document_range getActiveDocumentContext modifyRange
#'
run_addin <- function(addin_name) {
  # Select which addin has to be used.
  addin_function <- switch(addin_name,
    "comment_code" = comment_code,
    "complete_code" = complete_code,
    "create_unit_tests" = create_unit_tests,
    "create_variable_name" = create_variable_name,
    "document_code" = document_code,
    "explain_code" = explain_code,
    "find_issues_in_code" = find_issues_in_code,
    "optimize_code" = optimize_code,
    "refactor_code" = refactor_code,
    stop("`addin_name` not found.")
  )
  # Get the selected code.
  doc_context <- getActiveDocumentContext()
  selected_code <- doc_context$selection[[1]]$text
  is_full_file <- all(nchar(selected_code) == 0)
  # If no code is selected, use the whole file.
  if (is_full_file) {
    selected_code <- doc_context$contents
  }
  selected_code <- paste0(selected_code, collapse = "\n")
  # Apply the addin function.
  out <- addin_function(selected_code)
  if (as.logical(Sys.getenv("OPENAI_ADDIN_REPLACE", FALSE))) {
    doc_range <- doc_context$selection[[1]]$range
    if (is_full_file) {
      doc_range <- as.document_range(c(c(0, 0), c(Inf, Inf)))
    }
    modifyRange(doc_range, out, doc_context$id)
  } else if (get_verbosity()) {
    message(paste0("\n*** ChatGPT output:\n\n", out, "\n"))
  } else {
    warning("Please set one of `OPENAI_ADDIN_REPLACE=TRUE` or `OPENAI_VERBOSE=TRUE`")
  }
  invisible(NULL)
}

run_addin_comment_code <- function() run_addin("comment_code")
run_addin_complete_code <- function() run_addin("complete_code")
run_addin_create_unit_tests <- function() run_addin("create_unit_tests")
run_addin_create_variable_name <- function() run_addin("create_variable_name")
run_addin_document_code <- function() run_addin("document_code")
run_addin_explain_code <- function() run_addin("explain_code")
run_addin_find_issues_in_code <- function() run_addin("find_issues_in_code")
run_addin_optimize_code <- function() run_addin("optimize_code")
run_addin_refactor_code <- function() run_addin("refactor_code")

#' Ask ChatGPT
#'
#' Opens an interactive chat session with ChatGPT
#'
#' @importFrom miniUI gadgetTitleBar miniPage
#' @importFrom shiny actionButton br icon observeEvent onStop runGadget stopApp textAreaInput
#' @importFrom shiny updateTextAreaInput wellPanel
#' @importFrom utils getFromNamespace
#'
run_addin_ask_chatgpt <- function() {
  reset_chat_session()
  ui <- miniPage(wellPanel(
    gadgetTitleBar("Ask ChatGPT", NULL),
    textAreaInput("question", "Question:", width = "100%", height = "150px"),
    actionButton("ask_button", "Ask", icon("paper-plane")),
    br(), br(),
    textAreaInput("answer", "Answer:", width = "100%", height = "150px")
  ))
  server <- function(input, output, session) {
    observeEvent(input$ask_button, {
      chatgpt_reply <- ask_chatgpt(input$question)
      if (get_verbosity()) {
        message(paste0("\n*** ChatGPT output:\n\n", chatgpt_reply, "\n"))
      }
      updateTextAreaInput(session, "answer", value = chatgpt_reply)
    })
    observeEvent(input$done, {
      reset_chat_session()
      stopApp()
    })
    onStop(reset_chat_session)
  }
  runGadget(ui, server)
}

#' Get GPT Completions Endpoint
#'
#' @param prompt The prompt to generate completions for.
#' @param openai_api_key OpenAI's API key.
#' @param messages Available variable, to send the needed messages list to ChatGPT.
#'
#' @importFrom httr add_headers content content_type_json POST use_proxy
#' @importFrom jsonlite toJSON
#'
gpt_get_completions <- function(prompt, openai_api_key = Sys.getenv("OPENAI_API_KEY"),
                                messages = NULL) {
  if (nchar(openai_api_key) == 0) {
    stop("`OPENAI_API_KEY` not provided.")
  }
  # See https://platform.openai.com/docs/api-reference/chat .
  params <- list(
    model = Sys.getenv("OPENAI_MODEL", "gpt-4o-mini"),
    max_tokens = as.numeric(Sys.getenv("OPENAI_MAX_TOKENS", 256)),
    temperature = as.numeric(Sys.getenv("OPENAI_TEMPERATURE", 1)),
    top_p = as.numeric(Sys.getenv("OPENAI_TOP_P", 1)),
    frequency_penalty = as.numeric(Sys.getenv("OPENAI_FREQUENCY_PENALTY", 0)),
    presence_penalty = as.numeric(Sys.getenv("OPENAI_PRESENCE_PENALTY", 0)),
    logprobs = as.logical(Sys.getenv("OPENAI_LOGPROBS", FALSE))
  )
  if (get_verbosity()) {
    message(paste0("\n*** ChatGPT input:\n\n", prompt, "\n"))
  }
  return_language <- Sys.getenv("OPENAI_RETURN_LANGUAGE")
  if (nchar(return_language) > 0) {
    return_language <- paste0("You return all your replies in ", return_language, ".")
  }
  if (is.null(messages)) {
    messages <- list(
      list(
        role = "system",
        content = paste(
          "You are a helpful assistant with extensive knowledge of R programming.",
          return_language
        )
      ),
      list(role = "user", content = prompt)
    )
  } else {
    # If there are messages provided, then add the `return_language` if available.
    messages[[which(sapply(messages, function(message) message$role == "system"))]]$content <-
      paste(
        messages[[which(sapply(messages, function(message) message$role == "system"))]]$content,
        return_language
      )
  }
  # Get the proxy to use, if provided.
  proxy <- NULL
  if (nchar(Sys.getenv("OPENAI_PROXY")) > 0) {
    proxy <- Sys.getenv("OPENAI_PROXY")
    if (grepl("^(?:\\d{1,3}\\.){3}\\d{1,3}:\\d{2,5}$", proxy)) {
      proxy <- use_proxy(gsub(":.*", "", proxy), as.numeric(gsub(".*:", "", proxy)))
    } else {
      stop("Invalid proxy provided in `OPENAI_PROXY`: ", proxy)
    }
  }
  # Run the API query.
  final_res <- list()
  keep_querying <- TRUE
  while (keep_querying) {
    post_res <- POST(
      paste0(api_url, "/chat/completions"),
      add_headers("Authorization" = paste("Bearer", openai_api_key)),
      content_type_json(),
      body = toJSON(c(params, list(messages = messages)), auto_unbox = TRUE),
      proxy
    )
    if (!post_res$status_code %in% 200:299) {
      stop(content(post_res))
    }
    if (get_verbosity() > 1) {
      # If verbose is over 1, show the ongoing GPT response.
      message(content(post_res, as = "text", encoding = "UTF-8"))
    }
    post_res <- content(post_res)
    final_res <- append(final_res, list(post_res))
    # In the case the finish_reason is the length of the message, then we need to keep querying.
    keep_querying <- all(sapply(post_res$choices, function(x) x$finish_reason == "length"))
    # And update the messages sent to ChatGPT, in order to continue the current session.
    messages <- append(
      append(
        messages,
        list(list(role = "assistant", content = parse_response(list(post_res), verbosity = 0)))
      ),
      list(list(role = "user", content = "continue"))
    )
  }
  final_res
}

#' Build Prompt Content
#'
#' @param question The question to ask ChatGPT.
#' @param images A list of images to attach to the question. It could be a list of URLs or paths.
#'
#' @importFrom xfun base64_encode
#'
build_prompt_content <- function(question, images) {
  if (length(images) == 0) {
    return(question)
  }
  prompt_content <- list(list(type = "text", text = question))
  append(prompt_content, lapply(images, function(image) {
    # If it's a local file, then transform it to base 64 encoding. If not, return the "URL".
    image_url <- image
    if (file.exists(image)) {
      image_url <- paste0("data:image/jpeg;base64,", base64_encode(image))
    }
    list(type = "image_url", image_url = list(url = image_url))
  }))
}

#' Parse OpenAI API Response
#'
#' Takes the raw response from the OpenAI API and extracts the text content from it.
#'
#' @param raw_responses The raw response object returned by the OpenAI API.
#' @param verbosity The verbosity level for this function.
#'
#' @return Returns a character vector containing the text content of the response.
#'
parse_response <- function(raw_responses, verbosity = get_verbosity()) {
  # Parse the message content of the list of raw_responses. Trim those messages, and paste them.
  parsed_response <- paste(trimws(sapply(raw_responses, function(response) {
    sapply(response$choices, function(x) x$message$content)
  })), collapse = "")
  if (verbosity > 2) {
    # If we are in 3-verbose mode, add the raw_responses as an attribute to the return object.
    attr(parsed_response, "raw_responses") <- raw_responses
  }
  parsed_response
}

#' Generate an Image With DALL-E 3
#'
#' @param prompt The prompt for image generation.
#' @param out_file The path where to save the generated image.
#' @param openai_api_key OpenAI's API key.
#'
#' @importFrom httr add_headers content content_type_json POST stop_for_status
#' @importFrom jsonlite fromJSON toJSON
#' @importFrom utils download.file
#'
#' @export
#'
generate_image <- function(prompt, out_file = tempfile(fileext = ".png"),
                           openai_api_key = Sys.getenv("OPENAI_API_KEY")) {
  post_res <- POST(
    paste0(api_url, "/images/generations"),
    add_headers("Authorization" = paste("Bearer", openai_api_key)),
    content_type_json(),
    body = toJSON(
      list(model = "dall-e-3", prompt = prompt, n = 1, size = "1024x1024"),
      auto_unbox = TRUE
    )
  )
  stop_for_status(post_res)
  download.file(fromJSON(content(post_res, as = "text", encoding = "UTF-8"))$data$url, out_file)
  return(out_file)
}

#' ChatGPT: List Models
#'
#' @param openai_api_key OpenAI's API key.
#'
#' @examples
#' \dontrun{
#' list_models()
#' }
#'
#' @importFrom httr add_headers content GET stop_for_status
#' @importFrom jsonlite fromJSON
#'
#' @return A data.frame with the available models to be used by OpenAI's API.
#'
#' @export
#'
list_models <- function(openai_api_key = Sys.getenv("OPENAI_API_KEY")) {
  get_res <- GET(
    paste0(api_url, "/models"),
    add_headers("Authorization" = paste("Bearer", openai_api_key))
  )
  stop_for_status(get_res)
  fromJSON(content(get_res, as = "text", encoding = "UTF-8"))$data
}

#' ChatGPT: Create Unit Tests
#'
#' Create `{testthat}` test cases for the code.
#'
#' @param code The code for which to create unit tests by ChatGPT. If not provided, it will use
#'   what's copied on the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(create_unit_tests("squared_numbers <- function(numbers) {\n  numbers ^ 2\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
create_unit_tests <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0(
    "Using testthat 3e, version over 3.0.0, create a full testthat file, with test cases for the ",
    'following R code: "', code, '"'
  )
  parse_response(gpt_get_completions(prompt))
}

This code segment functions to: Retrieve stored conversation context based on an identifier value, provide default system interaction parameters when invalid references are detected, and maintain baseline communication settings for subsequent dialogue processing.

```r
[TASK_BEGIN]
function(session_id = "1") {
    if (is.null(session_id)) {
        return(list())
    }
    # In a real implementation, this would retrieve session from storage
    list(id = session_id, messages = list())
}
[TASK_END]
```

function(session_id = "1") {
    if (is.null(session_id)) {
        return(list())
    }
    list(id = session_id, messages = list())
}


#' Reset Chat Session
#'
#' This function is intended to be used with `ask_chatgpt`. If we are using `ask_chatgpt` to chat with ChatGPT, and
#' we want to start a new conversation, we must call `reset_chat_session`.
#'
#' @param system_role ChatGPT's role as an AI assistant.
#' @param session_id The ID of the session to be used. If `NULL`, this function will have no effect.
#'
#' @export
#'
reset_chat_session <- function(system_role = "You are a helpful assistant.", session_id = "1") {
  if (is.null(session_id)) {
    return()
  }
  if (is.list(system_role)) {
    # If `system_role` is a list, then it is a ChatGPT session object.
    session <- system_role
  } else {
    # Otherwise, it's a string specifying ChatGPT's role.
    session <- list(list(role = "system", content = system_role))
  }
  all_sessions <- get("chat_session_messages", envir = .state)
  all_sessions[[as.character(session_id)]] <- session
  assign("chat_session_messages", all_sessions, envir = .state)
}

#' 'OpenAI's 'ChatGPT' <https://chat.openai.com/> coding assistant for 'RStudio'. A set
#' of functions and 'RStudio' addins that aim to help the R developer in tedious coding tasks.
#'
"_PACKAGE"

.state <- new.env(parent = emptyenv())

# Empty chat session messages at startup.
assign("chat_session_messages", list(), envir = .state)

api_url <- Sys.getenv("OPENAI_API_URL", "https://api.openai.com/v1")

#' Get Verbosity Level
#'
get_verbosity <- function() {
  # `OPENAI_VERBOSE` should be one of `numeric` or `FALSE`/`TRUE`. But we'll return it as numeric.
  suppressWarnings(max(
    as.logical(Sys.getenv("OPENAI_VERBOSE", TRUE)),
    as.numeric(Sys.getenv("OPENAI_VERBOSE", TRUE)),
    na.rm = TRUE
  ))
}

#' ChatGPT: Document Code (in roxygen2 format)
#'
#' @param code The code to be documented by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(document_code("square_numbers <- function(numbers) numbers ** 2"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
document_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Document, in roxygen2 format, this R function: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Comment Code
#'
#' @param code The code to be commented by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(comment_code("for (i in 1:10) {\n  print(i ** 2)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
comment_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Add inline comments to the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Create Variable Name
#'
#' @param code The code for which to give a variable name to its result. If not provided, it will
#'   use what's copied on the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(create_variable_name("sapply(1:10, function(i) i ** 2)"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
create_variable_name <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Give a good variable name to the result of the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Explain Code
#'
#' @param code The code to be explained by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(explain_code("for (i in 1:10) {\n  print(i ** 2)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
explain_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Explain the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Optimize Code
#'
#' @param code The code to be optimized by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(optimize_code("i <- 10\nwhile (i > 0) {\n  i <- i - 1\n  print(i)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
optimize_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Optimize the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Refactor Code
#'
#' @param code The code to be refactored by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(refactor_code("i <- 10\nwhile (i > 0) {\n  i <- i - 1\n  print(i)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
refactor_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Refactor the following R code, returning valid R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Find Issues in Code
#'
#' @param code The code to be analyzed by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(find_issues_in_code("i <- 0\nwhile (i < 0) {\n  i <- i - 1\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
find_issues_in_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Find issues or bugs in the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Complete Code
#'
#' @param code The code to be completed by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(complete_code("# A function to square each element of a vector\nsquare_each <- function("))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
complete_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Complete the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

This code segment functions to: Retrieve stored conversation context based on an identifier value, returning a predefined default configuration when no valid identifier is provided or when no matching context exists in persistent storage.

```r
[TASK_BEGIN]
function(session_id = NULL) {
    if (is.null(session_id)) {
        return(list())
    }
}
[TASK_END]
```


#' Get GPT Completions Endpoint
#'
#' @param prompt The prompt to generate completions for.
#' @param openai_api_key OpenAI's API key.
#' @param messages Available variable, to send the needed messages list to ChatGPT.
#'
#' @importFrom httr add_headers content content_type_json POST use_proxy
#' @importFrom jsonlite toJSON
#'
gpt_get_completions <- function(prompt, openai_api_key = Sys.getenv("OPENAI_API_KEY"),
                                messages = NULL) {
  if (nchar(openai_api_key) == 0) {
    stop("`OPENAI_API_KEY` not provided.")
  }
  # See https://platform.openai.com/docs/api-reference/chat .
  params <- list(
    model = Sys.getenv("OPENAI_MODEL", "gpt-4o-mini"),
    max_tokens = as.numeric(Sys.getenv("OPENAI_MAX_TOKENS", 256)),
    temperature = as.numeric(Sys.getenv("OPENAI_TEMPERATURE", 1)),
    top_p = as.numeric(Sys.getenv("OPENAI_TOP_P", 1)),
    frequency_penalty = as.numeric(Sys.getenv("OPENAI_FREQUENCY_PENALTY", 0)),
    presence_penalty = as.numeric(Sys.getenv("OPENAI_PRESENCE_PENALTY", 0)),
    logprobs = as.logical(Sys.getenv("OPENAI_LOGPROBS", FALSE))
  )
  if (get_verbosity()) {
    message(paste0("\n*** ChatGPT input:\n\n", prompt, "\n"))
  }
  return_language <- Sys.getenv("OPENAI_RETURN_LANGUAGE")
  if (nchar(return_language) > 0) {
    return_language <- paste0("You return all your replies in ", return_language, ".")
  }
  if (is.null(messages)) {
    messages <- list(
      list(
        role = "system",
        content = paste(
          "You are a helpful assistant with extensive knowledge of R programming.",
          return_language
        )
      ),
      list(role = "user", content = prompt)
    )
  } else {
    # If there are messages provided, then add the `return_language` if available.
    messages[[which(sapply(messages, function(message) message$role == "system"))]]$content <-
      paste(
        messages[[which(sapply(messages, function(message) message$role == "system"))]]$content,
        return_language
      )
  }
  # Get the proxy to use, if provided.
  proxy <- NULL
  if (nchar(Sys.getenv("OPENAI_PROXY")) > 0) {
    proxy <- Sys.getenv("OPENAI_PROXY")
    if (grepl("^(?:\\d{1,3}\\.){3}\\d{1,3}:\\d{2,5}$", proxy)) {
      proxy <- use_proxy(gsub(":.*", "", proxy), as.numeric(gsub(".*:", "", proxy)))
    } else {
      stop("Invalid proxy provided in `OPENAI_PROXY`: ", proxy)
    }
  }
  # Run the API query.
  final_res <- list()
  keep_querying <- TRUE
  while (keep_querying) {
    post_res <- POST(
      paste0(api_url, "/chat/completions"),
      add_headers("Authorization" = paste("Bearer", openai_api_key)),
      content_type_json(),
      body = toJSON(c(params, list(messages = messages)), auto_unbox = TRUE),
      proxy
    )
    if (!post_res$status_code %in% 200:299) {
      stop(content(post_res))
    }
    if (get_verbosity() > 1) {
      # If verbose is over 1, show the ongoing GPT response.
      message(content(post_res, as = "text", encoding = "UTF-8"))
    }
    post_res <- content(post_res)
    final_res <- append(final_res, list(post_res))
    # In the case the finish_reason is the length of the message, then we need to keep querying.
    keep_querying <- all(sapply(post_res$choices, function(x) x$finish_reason == "length"))
    # And update the messages sent to ChatGPT, in order to continue the current session.
    messages <- append(
      append(
        messages,
        list(list(role = "assistant", content = parse_response(list(post_res), verbosity = 0)))
      ),
      list(list(role = "user", content = "continue"))
    )
  }
  final_res
}

#' Ask ChatGPT
#'
#' Note: See also `reset_chat_session`.
#'
#' @param question The question to ask ChatGPT.
#' @param session_id The ID of the session to be used. We can have different conversations by using
#'   different session IDs.
#' @param openai_api_key OpenAI's API key.
#' @param images A list of images to attach to the question. It could be a list of URLs or paths.
#'
#' @examples
#' \dontrun{
#' cat(ask_chatgpt("What do you think about R language?"))
#' }
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
ask_chatgpt <- function(question, session_id = "1", openai_api_key = Sys.getenv("OPENAI_API_KEY"),
                        images = NULL) {
  # Get the existing chat session messages, and add the new message.
  chat_session_messages <- append(get_chat_session(session_id), list(
    list(role = "user", content = build_prompt_content(question, images))
  ))
  # Send the query to ChatGPT.
  chat_gpt_reply <- parse_response(
    gpt_get_completions(question, openai_api_key, chat_session_messages)
  )
  chat_session_messages <- append(chat_session_messages, list(
    list(role = "assistant", content = chat_gpt_reply)
  ))
  # Update the chat session messages with the new question and the reply.
  reset_chat_session(chat_session_messages, session_id)
  chat_gpt_reply
}

#' Run a ChatGPT RStudio Addin
#'
#' @param addin_name The name of the adding to execute.
#'
#' @importFrom rstudioapi as.document_range getActiveDocumentContext modifyRange
#'
run_addin <- function(addin_name) {
  # Select which addin has to be used.
  addin_function <- switch(addin_name,
    "comment_code" = comment_code,
    "complete_code" = complete_code,
    "create_unit_tests" = create_unit_tests,
    "create_variable_name" = create_variable_name,
    "document_code" = document_code,
    "explain_code" = explain_code,
    "find_issues_in_code" = find_issues_in_code,
    "optimize_code" = optimize_code,
    "refactor_code" = refactor_code,
    stop("`addin_name` not found.")
  )
  # Get the selected code.
  doc_context <- getActiveDocumentContext()
  selected_code <- doc_context$selection[[1]]$text
  is_full_file <- all(nchar(selected_code) == 0)
  # If no code is selected, use the whole file.
  if (is_full_file) {
    selected_code <- doc_context$contents
  }
  selected_code <- paste0(selected_code, collapse = "\n")
  # Apply the addin function.
  out <- addin_function(selected_code)
  if (as.logical(Sys.getenv("OPENAI_ADDIN_REPLACE", FALSE))) {
    doc_range <- doc_context$selection[[1]]$range
    if (is_full_file) {
      doc_range <- as.document_range(c(c(0, 0), c(Inf, Inf)))
    }
    modifyRange(doc_range, out, doc_context$id)
  } else if (get_verbosity()) {
    message(paste0("\n*** ChatGPT output:\n\n", out, "\n"))
  } else {
    warning("Please set one of `OPENAI_ADDIN_REPLACE=TRUE` or `OPENAI_VERBOSE=TRUE`")
  }
  invisible(NULL)
}

run_addin_comment_code <- function() run_addin("comment_code")
run_addin_complete_code <- function() run_addin("complete_code")
run_addin_create_unit_tests <- function() run_addin("create_unit_tests")
run_addin_create_variable_name <- function() run_addin("create_variable_name")
run_addin_document_code <- function() run_addin("document_code")
run_addin_explain_code <- function() run_addin("explain_code")
run_addin_find_issues_in_code <- function() run_addin("find_issues_in_code")
run_addin_optimize_code <- function() run_addin("optimize_code")
run_addin_refactor_code <- function() run_addin("refactor_code")

#' Ask ChatGPT
#'
#' Opens an interactive chat session with ChatGPT
#'
#' @importFrom miniUI gadgetTitleBar miniPage
#' @importFrom shiny actionButton br icon observeEvent onStop runGadget stopApp textAreaInput
#' @importFrom shiny updateTextAreaInput wellPanel
#' @importFrom utils getFromNamespace
#'
run_addin_ask_chatgpt <- function() {
  reset_chat_session()
  ui <- miniPage(wellPanel(
    gadgetTitleBar("Ask ChatGPT", NULL),
    textAreaInput("question", "Question:", width = "100%", height = "150px"),
    actionButton("ask_button", "Ask", icon("paper-plane")),
    br(), br(),
    textAreaInput("answer", "Answer:", width = "100%", height = "150px")
  ))
  server <- function(input, output, session) {
    observeEvent(input$ask_button, {
      chatgpt_reply <- ask_chatgpt(input$question)
      if (get_verbosity()) {
        message(paste0("\n*** ChatGPT output:\n\n", chatgpt_reply, "\n"))
      }
      updateTextAreaInput(session, "answer", value = chatgpt_reply)
    })
    observeEvent(input$done, {
      reset_chat_session()
      stopApp()
    })
    onStop(reset_chat_session)
  }
  runGadget(ui, server)
}

#' Build Prompt Content
#'
#' @param question The question to ask ChatGPT.
#' @param images A list of images to attach to the question. It could be a list of URLs or paths.
#'
#' @importFrom xfun base64_encode
#'
build_prompt_content <- function(question, images) {
  if (length(images) == 0) {
    return(question)
  }
  prompt_content <- list(list(type = "text", text = question))
  append(prompt_content, lapply(images, function(image) {
    # If it's a local file, then transform it to base 64 encoding. If not, return the "URL".
    image_url <- image
    if (file.exists(image)) {
      image_url <- paste0("data:image/jpeg;base64,", base64_encode(image))
    }
    list(type = "image_url", image_url = list(url = image_url))
  }))
}

#' Parse OpenAI API Response
#'
#' Takes the raw response from the OpenAI API and extracts the text content from it.
#'
#' @param raw_responses The raw response object returned by the OpenAI API.
#' @param verbosity The verbosity level for this function.
#'
#' @return Returns a character vector containing the text content of the response.
#'
parse_response <- function(raw_responses, verbosity = get_verbosity()) {
  # Parse the message content of the list of raw_responses. Trim those messages, and paste them.
  parsed_response <- paste(trimws(sapply(raw_responses, function(response) {
    sapply(response$choices, function(x) x$message$content)
  })), collapse = "")
  if (verbosity > 2) {
    # If we are in 3-verbose mode, add the raw_responses as an attribute to the return object.
    attr(parsed_response, "raw_responses") <- raw_responses
  }
  parsed_response
}

#' ChatGPT: Create Unit Tests
#'
#' Create `{testthat}` test cases for the code.
#'
#' @param code The code for which to create unit tests by ChatGPT. If not provided, it will use
#'   what's copied on the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(create_unit_tests("squared_numbers <- function(numbers) {\n  numbers ^ 2\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
create_unit_tests <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0(
    "Using testthat 3e, version over 3.0.0, create a full testthat file, with test cases for the ",
    'following R code: "', code, '"'
  )
  parse_response(gpt_get_completions(prompt))
}

#' Reset Chat Session
#'
#' This function is intended to be used with `ask_chatgpt`. If we are using `ask_chatgpt` to chat with ChatGPT, and
#' we want to start a new conversation, we must call `reset_chat_session`.
#'
#' @param system_role ChatGPT's role as an AI assistant.
#' @param session_id The ID of the session to be used. If `NULL`, this function will have no effect.
#'
#' @export
#'
reset_chat_session <- function(system_role = "You are a helpful assistant.", session_id = "1") {
  if (is.null(session_id)) {
    return()
  }
  if (is.list(system_role)) {
    # If `system_role` is a list, then it is a ChatGPT session object.
    session <- system_role
  } else {
    # Otherwise, it's a string specifying ChatGPT's role.
    session <- list(list(role = "system", content = system_role))
  }
  all_sessions <- get("chat_session_messages", envir = .state)
  all_sessions[[as.character(session_id)]] <- session
  assign("chat_session_messages", all_sessions, envir = .state)
}

#' Get Chat Session
#'
#' @param session_id The ID of the session to be used. If `NULL`, it will return an empty session.
#'
get_chat_session <- function(session_id = "1") {
  default_session <- list(list(role = "system", content = "You are a helpful assistant."))
  if (is.null(session_id)) {
    return(default_session)
  }
  session <- get("chat_session_messages", envir = .state)[[as.character(session_id)]]
  # If the session was not found, then it's a new (default) session.
  if (is.null(session)) {
    session <- default_session
  }
  session
}

#' Get Verbosity Level
#'
get_verbosity <- function() {
  # `OPENAI_VERBOSE` should be one of `numeric` or `FALSE`/`TRUE`. But we'll return it as numeric.
  suppressWarnings(max(
    as.logical(Sys.getenv("OPENAI_VERBOSE", TRUE)),
    as.numeric(Sys.getenv("OPENAI_VERBOSE", TRUE)),
    na.rm = TRUE
  ))
}

#' ChatGPT: Document Code (in roxygen2 format)
#'
#' @param code The code to be documented by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(document_code("square_numbers <- function(numbers) numbers ** 2"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
document_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Document, in roxygen2 format, this R function: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Comment Code
#'
#' @param code The code to be commented by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(comment_code("for (i in 1:10) {\n  print(i ** 2)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
comment_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Add inline comments to the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

This code segment functions to: Submit a request to an external AI service for visual content creation based on textual input, process the API response to retrieve the generated resource, and download the resulting media asset to local storage while handling authorization and error checking.

```r
[TASK_BEGIN]
function(prompt, out_file = tempfile(fileext = ".png"),
         openai_api_key = Sys.getenv("OPENAI_API_KEY")) {
    if (missing(prompt) || nchar(prompt) == 0) {
        stop("A text prompt must be specified")
    }

    if (nchar(openai_api_key) == 0) {
        stop("API key required in openai_api_key parameter or OPENAI_API_KEY environment variable")
    }

    response <- httr::POST(
        url = "https://api.openai.com/v1/images/generations",
        httr::add_headers(
            "Authorization" = paste("Bearer", openai_api_key),
            "Content-Type" = "application/json"
        ),
        body = jsonlite::toJSON(list(
            model = "dall-e-3",
            prompt = prompt,
            n = 1,
            size = "1024x1024"
        ), auto_unbox = TRUE)
    )

    httr::stop_for_status(response)
    parsed <- jsonlite::fromJSON(httr::content(response, "text", encoding = "UTF-8"))
    image_url <- parsed$data$url

    utils::download.file(image_url, out_file, mode = "wb")
    invisible(out_file)
}
[TASK_END]
```


#' Get GPT Completions Endpoint
#'
#' @param prompt The prompt to generate completions for.
#' @param openai_api_key OpenAI's API key.
#' @param messages Available variable, to send the needed messages list to ChatGPT.
#'
#' @importFrom httr add_headers content content_type_json POST use_proxy
#' @importFrom jsonlite toJSON
#'
gpt_get_completions <-

function(prompt, openai_api_key = Sys.getenv("OPENAI_API_KEY"),
                                messages = NULL) {
  if (nchar(openai_api_key) == 0) {
    stop("`OPENAI_API_KEY` not provided.")
  }
  params <- list(
    model = Sys.getenv("OPENAI_MODEL", "gpt-4o-mini"),
    max_tokens = as.numeric(Sys.getenv("OPENAI_MAX_TOKENS", 256)),
    temperature = as.numeric(Sys.getenv("OPENAI_TEMPERATURE", 1)),
    top_p = as.numeric(Sys.getenv("OPENAI_TOP_P", 1)),
    frequency_penalty = as.numeric(Sys.getenv("OPENAI_FREQUENCY_PENALTY", 0)),
    presence_penalty = as.numeric(Sys.getenv("OPENAI_PRESENCE_PENALTY", 0)),
    logprobs = as.logical(Sys.getenv("OPENAI_LOGPROBS", FALSE))
  )
  if (get_verbosity()) {
    message(paste0("\n*** ChatGPT input:\n\n", prompt, "\n"))
  }
  return_language <- Sys.getenv("OPENAI_RETURN_LANGUAGE")
  if (nchar(return_language) > 0) {
    return_language <- paste0("You return all your replies in ", return_language, ".")
  }
  if (is.null(messages)) {
    messages <- list(
      list(
        role = "system",
        content = paste(
          "You are a helpful assistant with extensive knowledge of R programming.",
          return_language
        )
      ),
      list(role = "user", content = prompt)
    )
  } else {
    messages[[which(sapply(messages, function(message) message$role == "system"))]]$content <-
      paste(
        messages[[which(sapply(messages, function(message) message$role == "system"))]]$content,
        return_language
      )
  }
  proxy <- NULL
  if (nchar(Sys.getenv("OPENAI_PROXY")) > 0) {
    proxy <- Sys.getenv("OPENAI_PROXY")
    if (grepl("^(?:\\d{1,3}\\.){3}\\d{1,3}:\\d{2,5}$", proxy)) {
      proxy <- use_proxy(gsub(":.*", "", proxy), as.numeric(gsub(".*:", "", proxy)))
    } else {
      stop("Invalid proxy provided in `OPENAI_PROXY`: ", proxy)
    }
  }
  final_res <- list()
  keep_querying <- TRUE
  while (keep_querying) {
    post_res <- POST(
      paste0(api_url, "/chat/completions"),
      add_headers("Authorization" = paste("Bearer", openai_api_key)),
      content_type_json(),
      body = toJSON(c(params, list(messages = messages)), auto_unbox = TRUE),
      proxy
    )
    if (!post_res$status_code %in% 200:299) {
      stop(content(post_res))
    }
    if (get_verbosity() > 1) {
      message(content(post_res, as = "text", encoding = "UTF-8"))
    }
    post_res <- content(post_res)
    final_res <- append(final_res, list(post_res))
    keep_querying <- all(sapply(post_res$choices, function(x) x$finish_reason == "length"))
    messages <- append(
      append(
        messages,
        list(list(role = "assistant", content = parse_response(list(post_res), verbosity = 0)))
      ),
      list(list(role = "user", content = "continue"))
    )
  }
  final_res
}

#' Generate an Image With DALL-E 3
#'
#' @param prompt The prompt for image generation.
#' @param out_file The path where to save the generated image.
#' @param openai_api_key OpenAI's API key.
#'
#' @importFrom httr add_headers content content_type_json POST stop_for_status
#' @importFrom jsonlite fromJSON toJSON
#' @importFrom utils download.file
#'
#' @export
#'
generate_image <- function(prompt, out_file = tempfile(fileext = ".png"),
                           openai_api_key = Sys.getenv("OPENAI_API_KEY")) {
  post_res <- POST(
    paste0(api_url, "/images/generations"),
    add_headers("Authorization" = paste("Bearer", openai_api_key)),
    content_type_json(),
    body = toJSON(
      list(model = "dall-e-3", prompt = prompt, n = 1, size = "1024x1024"),
      auto_unbox = TRUE
    )
  )
  stop_for_status(post_res)
  download.file(fromJSON(content(post_res, as = "text", encoding = "UTF-8"))$data$url, out_file)
  return(out_file)
}

#' ChatGPT: List Models
#'
#' @param openai_api_key OpenAI's API key.
#'
#' @examples
#' \dontrun{
#' list_models()
#' }
#'
#' @importFrom httr add_headers content GET stop_for_status
#' @importFrom jsonlite fromJSON
#'
#' @return A data.frame with the available models to be used by OpenAI's API.
#'
#' @export
#'
list_models <- function(openai_api_key = Sys.getenv("OPENAI_API_KEY")) {
  get_res <- GET(
    paste0(api_url, "/models"),
    add_headers("Authorization" = paste("Bearer", openai_api_key))
  )
  stop_for_status(get_res)
  fromJSON(content(get_res, as = "text", encoding = "UTF-8"))$data
}

#' 'OpenAI's 'ChatGPT' <https://chat.openai.com/> coding assistant for 'RStudio'. A set
#' of functions and 'RStudio' addins that aim to help the R developer in tedious coding tasks.
#'
"_PACKAGE"

.state <- new.env(parent = emptyenv())

# Empty chat session messages at startup.
assign("chat_session_messages", list(), envir = .state)

api_url <- Sys.getenv("OPENAI_API_URL", "https://api.openai.com/v1")

#' ChatGPT: Create Variable Name
#'
#' @param code The code for which to give a variable name to its result. If not provided, it will
#'   use what's copied on the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(create_variable_name("sapply(1:10, function(i) i ** 2)"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
create_variable_name <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Give a good variable name to the result of the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Optimize Code
#'
#' @param code The code to be optimized by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(optimize_code("i <- 10\nwhile (i > 0) {\n  i <- i - 1\n  print(i)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
optimize_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Optimize the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Explain Code
#'
#' @param code The code to be explained by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(explain_code("for (i in 1:10) {\n  print(i ** 2)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
explain_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Explain the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Refactor Code
#'
#' @param code The code to be refactored by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(refactor_code("i <- 10\nwhile (i > 0) {\n  i <- i - 1\n  print(i)\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
refactor_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Refactor the following R code, returning valid R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Find Issues in Code
#'
#' @param code The code to be analyzed by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(find_issues_in_code("i <- 0\nwhile (i < 0) {\n  i <- i - 1\n}"))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
find_issues_in_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Find issues or bugs in the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

#' ChatGPT: Complete Code
#'
#' @param code The code to be completed by ChatGPT. If not provided, it will use what's copied on
#'   the clipboard.
#'
#' @examples
#' \dontrun{
#' cat(complete_code("# A function to square each element of a vector\nsquare_each <- function("))
#' }
#'
#' @importFrom clipr read_clip
#'
#' @return A character value with the response generated by ChatGPT.
#'
#' @export
#'
complete_code <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
  code <- paste(gsub('"', "'", code), collapse = "\n")
  prompt <- paste0('Complete the following R code: "', code, '"')
  parse_response(gpt_get_completions(prompt))
}

function(prompt, openai_api_key = Sys.getenv("OPENAI_API_KEY"),
                                messages = NULL) {}

This code segment functions to: Configure and execute iterative API requests to a language model service using specified parameters, handle response validation and continuation tokens, and manage system-level configurations including proxy settings and localization preferences.

```r
[TASK_BEGIN]
function(prompt, openai_api_key = Sys.getenv("OPENAI_API_KEY"), messages = NULL) {
    if (is.null(messages)) {
        messages <- list(list(role = "user", content = prompt))
    }

    body <- list(
        model = "gpt-3.5-turbo",
        messages = messages
    )

    response <- POST(
        url = "https://api.openai.com/v1/chat/completions",
        add_headers(Authorization = paste("Bearer", openai_api_key)),
        content_type_json(),
        body = jsonlite::toJSON(body, auto_unbox = TRUE)
    )

    if (response$status_code != 200) {
        stop(content(response, "text", encoding = "UTF-8"))
    }

    content <- jsonlite::fromJSON(content(response, "text", encoding = "UTF-8"))
    return(content$choices[[1]]$message$content)
}
[TASK_END]
```