# Provider configurations

#' Detect provider from model name or config
#' @noRd
.detect_provider <- function(model) {
    base <- .get_base()

    # Check base URL first
    if (!is.null(base)) {
        if (grepl("moonshot|kimi", base, ignore.case = TRUE)) {
            return("moonshot")
        }
        if (grepl("anthropic", base, ignore.case = TRUE)) {
            return("anthropic")
        }
        if (grepl("openai", base, ignore.case = TRUE)) {
            return("openai")
        }
        if (grepl("localhost|127\\.0\\.0\\.1", base)) {
            return("ollama")
        }
    }

    # Check model name
    if (!is.null(model)) {
        if (grepl("^claude", model, ignore.case = TRUE)) {
            return("anthropic")
        }
        if (grepl("^kimi|^moonshot-v1", model, ignore.case = TRUE)) {
            return("moonshot")
        }
        if (grepl("^(llama|mistral|phi|qwen|gemma|codellama|deepseek)", model,
                  ignore.case = TRUE)) {
            return("ollama")
        }
        # Check if model is installed in Ollama
        ollama_models <- tryCatch(
                                  list_ollama_models()$name,
                                  error = function(e) character(0)
        )
        if (model %in% ollama_models) {
            return("ollama")
        }
        if (grepl("^gpt|^o1|^o3", model, ignore.case = TRUE)) {
            return("openai")
        }
    }

    # Default to OpenAI-compatible
    "openai"
}

#' Get provider configuration
#' @noRd
.get_provider_config <- function(provider) {
    base <- .get_base()
    key <- .get_key()

    switch(provider,
           openai = list(provider = "openai",
                         base_url = base %||% "https://api.openai.com",
                         chat_path = "/v1/chat/completions",
                         api_key = .get_key("openai"),
                         default_model = "gpt-5.4-mini"),
           anthropic = list(
                            provider = "anthropic",
                            base_url = base %||% "https://api.anthropic.com",
                            chat_path = "/v1/messages",
                            api_key = .get_key("anthropic"),
                            default_model = "claude-sonnet-4-6"
        ),
           moonshot = list(
                           provider = "moonshot",
                           base_url = base %||% "https://api.moonshot.ai",
                           chat_path = "/v1/chat/completions",
                           api_key = .get_key("moonshot"),
                           default_model = "kimi-k2.5"
        ),
           ollama = list(
                         provider = "ollama",
                         base_url = base %||% "http://localhost:11434",
                         chat_path = "/v1/chat/completions",
                         api_key = NULL,
                         default_model = "qwen3.5:9b"
        ),
           stop("Unknown provider: ", provider, call. = FALSE)
    )
}

#' Default model for a provider
#'
#' Returns the model name `chat()` falls back to when the caller
#' doesn't specify one. Useful for client code that wants to display
#' the resolved model upfront (e.g., in a status line) without
#' duplicating the lookup table.
#'
#' @param provider Character. One of `"openai"`, `"anthropic"`,
#'   `"moonshot"`, `"ollama"`.
#' @return Character. The default model id for that provider.
#' @export
#' @examples
#' provider_default_model("anthropic")
#' provider_default_model("moonshot")
provider_default_model <- function(provider) {
    .get_provider_config(provider)$default_model
}

#' Chat with OpenAI
#'
#' Convenience wrapper for 'OpenAI' models.
#'
#' @inheritParams chat
#' @return The assistant's response as a character string, or a list when
#'   \code{history} is in use. See \code{\link{chat}} for details.
#' @export
#' @examples
#' \dontrun{
#' chat_openai("Explain quantum computing")
#' chat_openai("Write a haiku", model = "gpt-5.4-mini")
#' }
chat_openai <- function(prompt, model = "gpt-5.4-mini", ...) {
    chat(prompt, model = model, provider = "openai", ...)
}

#' Chat with Anthropic Claude
#'
#' Convenience wrapper for 'Anthropic' 'Claude' models.
#'
#' @inheritParams chat
#' @return The assistant's response as a character string, or a list when
#'   \code{history} is in use. See \code{\link{chat}} for details.
#' @export
#' @examples
#' \dontrun{
#' chat_claude("Explain the theory of relativity")
#' chat_claude("Write a poem", model = "claude-haiku-4-5")
#' }
chat_claude <- function(prompt, model = "claude-sonnet-4-6", ...) {
    chat(prompt, model = model, provider = "anthropic", ...)
}

#' Chat with Ollama
#'
#' Convenience wrapper for local 'Ollama' models.
#'
#' @inheritParams chat
#' @return The assistant's response as a character string, or a list when
#'   \code{history} is in use. See \code{\link{chat}} for details.
#' @export
#' @examples
#' \dontrun{
#' chat_ollama("What is machine learning?")
#' chat_ollama("Explain Docker", model = "mistral")
#' }
chat_ollama <- function(prompt, model = "qwen3.5:9b", ...) {
    chat(prompt, model = model, provider = "ollama", ...)
}

#' List Ollama models
#'
#' List all models downloaded in Ollama.
#'
#' @param base_url Character. Ollama server URL (default: http://localhost:11434).
#'
#' @return A data frame with model information (name, size, modified).
#' @export
#' @examples
#' \dontrun{
#' list_ollama_models()
#' }
list_ollama_models <- function(base_url = "http://localhost:11434") {
    url <- paste0(base_url, "/api/tags")

    h <- curl::new_handle()
    resp <- tryCatch(
                     curl::curl_fetch_memory(url, handle = h),
                     error = function(e) {
        stop("Cannot connect to Ollama at ", base_url,
             ". Is Ollama running?", call. = FALSE)
    }
    )

    if (resp$status_code >= 400) {
        stop("Ollama API error (", resp$status_code, ")", call. = FALSE)
    }

    data <- jsonlite::fromJSON(rawToChar(resp$content))

    if (is.null(data$models) || length(data$models) == 0) {
        message("No models found. Use 'ollama pull <model>' to download models.")
        return(invisible(data.frame(name = character(), size = numeric(), modified = character())))
    }

    data.frame(
               name = data$models$name,
               size = round(data$models$size / 1e9, 1),
               modified = as.character(as.POSIXct(data$models$modified_at))
    )
}

