# model usage ------------------------------------------------------------------
translate_to_model_usage <- function(chat) {
  tokens <- as.data.frame(chat$get_tokens())
  model <- chat$get_model()

  dots_list(
    !!model := list(
      input_tokens = sum(tokens$input, na.rm = TRUE),
      output_tokens = sum(tokens$output, na.rm = TRUE),
      total_tokens = sum(tokens$input, tokens$output, na.rm = TRUE),
      input_tokens_cache_write = 0,
      input_tokens_cache_read = sum(tokens$cached_input, na.rm = TRUE)
    )
  )
}

# given the list of solvers in a dataset, sum across all of their token usage
sum_model_usage <- function(solvers) {
  chat <- solvers[[1]]

  usage_per_solver <- lapply(
    solvers,
    function(chat) {
      translate_to_model_usage(chat)[[1]]
    }
  )
  res <- Reduce(function(x, y) Map(`+`, x, y), usage_per_solver)

  # TODO: ultimately, this needs to be per-model
  dots_list(!!chat$get_model() := res)
}

# output ----------------------------------------------------------------------
translate_to_output <- function(chat) {
  last_assistant_turn <- chat$last_turn()

  list(
    model = chat$get_model(),
    choices = translate_assistant_choices(last_assistant_turn),
    usage = translate_to_model_usage(chat)[[1]],
    time = turn_duration(last_assistant_turn)
  )
}

translate_assistant_choices <- function(turn) {
  text_contents <- first_text_contents(turn)
  text <- if (!is.null(text_contents)) text_contents@text else ""
  list(list(
    message = list(
      id = generate_id(),
      content = list(list(
        type = "text",
        text = text
      )),
      source = "generate",
      role = turn@role
    ),
    # turn@json$stop_reason gives the actual stop reason (often 'end_turn'), but
    # Inspect requires "Input should be 'stop', 'max_tokens', 'model_length',
    # 'tool_calls', 'content_filter' or 'unknown'" . (#7)
    stop_reason = "stop"
  ))
}

# miscellaneous ----------------------------------------------------------------
first_text_contents <- function(turn) {
  turn_contents <- turn@contents
  is_text <- vapply(turn_contents, inherits, logical(1), "ellmer::ContentText")
  turn_contents[[which(is_text)[1]]]
}

message_content_from_turn <- function(turn) {
  contents <- turn@contents
  if (length(contents) == 0) {
    return(turn@text)
  }

  entries <- lapply(contents, translate_ellmer_content)
  if (length(entries) > 0) {
    only_text <- all(vapply(
      entries,
      function(entry) {
        is.list(entry) && identical(entry$type, "text")
      },
      logical(1)
    ))
    if (only_text) {
      return(turn@text)
    }
  }

  entries
}

translate_ellmer_content <- function(content) {
  if (inherits(content, "ellmer::ContentText")) {
    return(list(type = "text", text = content@text))
  }

  if (inherits(content, "ellmer::ContentImageInline")) {
    media_type <- content@type %||% "image/png"
    return(list(
      type = "image",
      image = paste0("data:", media_type, ";base64,", content@data)
    ))
  }

  if (inherits(content, "ellmer::ContentImageRemote")) {
    detail <- content@detail %||% "auto"
    data_uri <- remote_url_to_data_uri(content@url)
    result <- list(type = "image", image = data_uri)
    if (!is.null(detail)) {
      result$detail <- detail
    }
    return(result)
  }

  fallback <- paste0(
    utils::capture.output(content),
    collapse = "\n"
  )
  list(type = "text", text = fallback)
}

remote_url_to_data_uri <- function(url) {
  resp <- httr2::request(url) |>
    httr2::req_perform()

  content_type <- httr2::resp_content_type(resp)
  raw_data <- httr2::resp_body_raw(resp)

  base64_data <- jsonlite::base64_enc(raw_data)

  paste0("data:", content_type, ";base64,", base64_data)
}

is_content_list <- function(x) {
  if (!is.list(x) || length(x) == 0) {
    return(FALSE)
  }

  first <- x[[1]]
  is.list(first) && !is.null(first$type)
}

ensure_content_list <- function(content) {
  if (is_content_list(content)) {
    return(content)
  }

  list(list(type = "text", text = as.character(content)))
}

has_tool_calls <- function(turns) {
  any(sapply(turns, function(turn) {
    any(sapply(turn@contents, function(content) {
      inherits(content, "ellmer::ContentToolRequest")
    }))
  }))
}

eval_log_timestamp <- function(time = Sys.time()) {
  timestamp <- format(time, "%Y-%m-%dT%H:%M:%S%z")
  gsub("([+-][0-9]{2})([0-9]{2})$", "\\1:\\2", timestamp)
}

generate_id <- function(length = 22) {
  chars <- c(letters, LETTERS, 0:9)
  paste0(sample(chars, length, replace = TRUE), collapse = "")
}

eval_log_filename <- function(eval_log) {
  paste0(
    clean_filename_component(eval_log$eval$created),
    "_",
    clean_filename_component(eval_log$eval$task_id),
    ".json"
  )
}

clean_filename_component <- function(x) {
  gsub("[_/:]", "-", x)
}

results_scores <- function(name, metrics) {
  if (length(metrics) == 0) {
    metrics <- c()
  }
  list(list(
    name = name,
    scorer = name,
    params = structure(list(), names = character(0)),
    metrics = metrics
  ))
}

rename_metric_fields <- function(metrics) {
  metrics$params <- metrics$arguments
  metrics$arguments <- NULL
  metrics
}
