#' Build Adjacency Matrices for Physical Interactions from STRING (POST API)
#'
#' Constructs weighted and binary adj matrices for physical protein-protein
#' interactions using a POST request to the STRING database API.
#'
#' @param genes A character vector of gene symbols or identifiers, e.g.,
#'   \code{c("TP53", "BRCA1", ...)}.
#' @param species Integer. NCBI taxonomy ID of the species. Default is
#'   \code{9606} (human).
#' @param required_score Integer in \\[0,1000\\]. Minimum confidence score for
#'   interactions. Default is \code{400}.
#' @param keep_all_genes Logical. If \code{TRUE} (default), includes all input
#'   genes in the final matrix even if unmapped.
#' @param verbose Logical. If \code{TRUE}, displays progress messages. Default
#'   is \code{TRUE}.
#'
#' @return A list containing:
#'   \itemize{
#'     \item \code{weighted}: A square numeric adjacency matrix with scores as
#'       weights.
#'     \item \code{binary}: A corresponding binary (0/1) adjacency matrix.
#'   }
#'
#' @details This function:
#'   \enumerate{
#'     \item Maps input genes to STRING internal IDs.
#'     \item Uses a POST request to retrieve physical protein-protein
#'       interactions from STRING.
#'     \item Builds a weighted adjacency matrix using the STRING combined score.
#'     \item Builds a binary adjacency matrix indicating presence/absence.
#'   }
#'
#' Genes not mapped to STRING are optionally retained as zero rows/columns if
#' \code{keep_all_genes = TRUE}.
#'
#' @note Requires packages: \pkg{STRINGdb}, \pkg{httr}, \pkg{jsonlite}.
#'
#' @importFrom httr POST content
#' @importFrom jsonlite fromJSON
#' @importFrom stats setNames
#' @export
#'
#' @examples
#' data(toy_counts)
#' genes <- selgene(
#'     object = toy_counts[[1]],
#'     top_n = 5,
#'     cell_type = "T_cells",
#'     cell_type_col = "CELL_TYPE",
#'     remove_rib = TRUE,
#'     remove_mt = TRUE,
#'     assay = "counts"
#' )
#'
#' str_res <- stringdb_adjacency(
#'     genes = genes,
#'     species = 9606,
#'     required_score = 900,
#'     keep_all_genes = FALSE
#' )
#' wadj_truth <- str_res$weighted
#' toy_adj_matrix <- str_res$binary
stringdb_adjacency <- function(
    genes,
    species = 9606,
    required_score = 400,
    keep_all_genes = TRUE,
    verbose = TRUE) {
    BiocBaseUtils::checkInstalled("STRINGdb")
    BiocBaseUtils::checkInstalled("httr")
    BiocBaseUtils::checkInstalled("jsonlite")

    if (length(genes) == 0) {
        stop("Please provide at least one gene in 'genes'.")
    }

    # Configure download method to handle SSL certificate issues
    # This is needed due to expired certificates on stringdb-downloads.org
    # Save original settings to restore later
    orig_download_method <- getOption("download.file.method")
    orig_download_extra <- getOption("download.file.extra")

    # Set options for download.file to skip SSL verification
    options(download.file.method = "curl")
    options(download.file.extra = "-k") # -k flag skips SSL verification

    # Also configure httr for API calls
    httr::set_config(httr::config(ssl_verifypeer = FALSE))

    # Ensure options are restored on exit
    on.exit(
        {
            options(download.file.method = orig_download_method)
            options(download.file.extra = orig_download_extra)
        },
        add = TRUE
    )

    if (verbose) message("Initializing STRINGdb...")

    string_db <- STRINGdb::STRINGdb$new(
        version = "11.5",
        species = species,
        score_threshold = required_score,
        input_directory = ""
    )

    # Map gene symbols to STRING IDs
    if (verbose) message("Mapping genes to STRING IDs...")
    mapped_genes <- string_db$map(
        data.frame(genes, stringsAsFactors = FALSE), "genes",
        removeUnmappedRows = FALSE
    )

    # Extract mapped and unmapped genes
    mapped_genes <- mapped_genes[!is.na(mapped_genes$STRING_id), ]
    unmapped_genes <- setdiff(genes, mapped_genes$genes)

    if (verbose) {
        message("Mapped ", nrow(mapped_genes), " genes to STRING IDs.")
        if (length(unmapped_genes) > 0 && keep_all_genes) {
            message(
                length(unmapped_genes),
                " genes were not found,included as ",
                "zero rows/columns."
            )
        }
    }

    if (nrow(mapped_genes) == 0) {
        stop("No valid STRING IDs found for the provided genes.")
    }

    # Prepare API request using STRING IDs
    if (verbose) {
        message("Retrieving **physical** interactions from STRING API...")
    }

    base_url <- "https://string-db.org/api/json/network"
    identifiers_str <- paste(mapped_genes$STRING_id, collapse = "
")

    res <- httr::POST(
        url = base_url,
        body = list(
            identifiers    = identifiers_str,
            species        = species,
            required_score = required_score,
            network_type   = "physical"
        ),
        encode = "form"
    )

    if (res$status_code != 200) {
        stop("STRING API query failed. Status code: ", res$status_code)
    }

    # Parse JSON response
    interactions <- jsonlite::fromJSON(
        httr::content(res, "text", encoding = "UTF-8")
    )

    if (!is.data.frame(interactions) || nrow(interactions) == 0) {
        if (verbose) message("No STRING physical interactions found.")
        return(list(
            weighted = matrix(0, length(genes), length(genes),
                dimnames = list(genes, genes)
            ),
            binary = matrix(0, length(genes), length(genes),
                dimnames = list(genes, genes)
            )
        ))
    }

    if (verbose) {
        message("Found ", nrow(interactions), " STRING physical interactions.")
    }

    # Ensure we use "score" instead of "combined_score"
    interactions$interaction_score <- interactions$score # Rename for clarity

    # Map STRING IDs to Gene Names
    id_to_gene <- setNames(mapped_genes$genes, mapped_genes$STRING_id)

    # Convert STRING IDs in interaction table to Gene Names
    interactions$gene_A <- id_to_gene[interactions$stringId_A]
    interactions$gene_B <- id_to_gene[interactions$stringId_B]

    # Remove interactions where gene names couldn't be mapped
    interactions <- interactions[!is.na(interactions$gene_A) &
        !is.na(interactions$gene_B), ]

    # Select genes to include in the adjacency matrix
    if (keep_all_genes) {
        final_gene_list <- genes
    } else {
        final_gene_list <- unique(c(interactions$gene_A, interactions$gene_B))
    }

    # Initialize p×p matrices (filled with 0s)
    p <- length(final_gene_list)
    weighted_mat <- matrix(0,
        nrow = p, ncol = p,
        dimnames = list(
            final_gene_list,
            final_gene_list
        )
    )

    # Populate adjacency matrix with STRING interaction data
    if (nrow(interactions) > 0) {
        for (i in seq_len(nrow(interactions))) {
            a <- interactions$gene_A[i]
            b <- interactions$gene_B[i]
            s <- interactions$interaction_score[i]

            if (!is.na(a) && !is.na(b) && a %in% final_gene_list &&
                b %in% final_gene_list) {
                weighted_mat[a, b] <- s
                weighted_mat[b, a] <- s
            }
        }
    }

    binary_mat <- ifelse(weighted_mat > 0, 1, 0)

    if (verbose) message("Adjacency matrices constructed successfully.")

    list(weighted = weighted_mat, binary = binary_mat)
}
