#' Build a shared nearest neighbor graph
#'
#' Build a shared nearest neighbor (SNN) graph where each node is a cell.
#' Edges are formed between cells that share one or more nearest neighbors, weighted by the number or ranking of those shared neighbors.
#' If two cells are close together but have distinct sets of neighbors, the corresponding edge is downweighted as the two cells are unlikely to be part of the same neighborhood.
#' In this manner, strongly weighted edges will only form within highly interconnected neighborhoods where many cells share the same neighbors.
#' This provides a more sophisticated definition of similarity between cells compared to a simpler (unweighted) nearest neighbor graph that just focuses on immediate proximity.
#'
#' @param x For \code{buildSnnGraph}, a numeric matrix where rows are dimensions and columns are cells,
#' typically containing a low-dimensional representation from, e.g., \code{\link{runPca}}.
#'
#' Alternatively, a named list of nearest-neighbor search results.
#' This should contain \code{index}, an integer matrix where rows are neighbors and columns are cells.
#' Each column contains 1-based indices for the nearest neighbors of the corresponding cell, ordered by increasing distance.
#' The number of neighbors for each cell should be equal to \code{num.neighbors}, otherwise a warning is raised.
#'
#' Alternatively, an index constructed by \code{\link[BiocNeighbors]{buildIndex}}.
#' @param num.neighbors Integer scalar specifying the number of neighbors to use to construct the graph.
#' Larger values increase the connectivity of the graph and reduce the granularity of subsequent community detection steps, at the cost of speed.
#' Ignored if \code{x} contains pre-computed neighbor search results. 
#' @param weight.scheme String specifying the weighting scheme to use for constructing the SNN graph.
#' This can be one of:
#' \itemize{
#' \item \code{"ranked"}, where the weight of the edge is defined by the smallest sum of ranks across all shared neighbors.
#' More shared neighbors, or shared neighbors that are close to both observations, will generally yield larger weights.
#' \item \code{"number"}, where the weight of the edge is the number of shared nearest neighbors between them. 
#' This is a simpler scheme that is also slightly faster but does not account for the ranking of neighbors within each set.
#' \item \code{"jaccard"}, where the weight of the edge is the Jaccard index of their neighbor sets,
#' This is a monotonic transformation of the weight used in \code{"number"}.
#' }
#' @param num.threads Integer scalar specifying the number of threads to use.
#' Only used if \code{x} is not a list of existing nearest-neighbor search results.
#' @param BNPARAM A \link[BiocNeighbors]{BiocNeighborParam} object specifying the algorithm to use.
#' Only used if \code{x} is not a list of existing nearest-neighbor search results.
#' @param as.pointer Logical scalar indicating whether to return an external pointer for direct use in \code{\link{clusterGraph}}.
#' This avoids the extra memory usage caused by conversion to/from an R list.
#'
#' @return If \code{as.pointer=FALSE}, a list is returned containing:
#' \itemize{
#' \item \code{vertices}, an integer scalar specifying the number of vertices in the graph (i.e., cells in \code{x}).
#' \item \code{edges}, an integer vector of 1-based indices for graph edges.
#' Pairs of values represent the endpoints of an (undirected) edge,
#' i.e., \code{edges[1:2]} form the first edge, \code{edges[3:4]} form the second edge and so on.
#' \item \code{weights}, a numeric vector of weights for each edge.
#' This has length equal to half the length of \code{edges}.
#' }
#'
#' If \code{as.pointer=TRUE}, an external pointer to the graph is returned that can be directly used in \code{\link{clusterGraph}}.
#'
#' @author Aaron Lun
#'
#' @seealso
#' The \code{build_snn_graph} function in \url{https://libscran.github.io/scran_graph_cluster/}.
#'
#' \code{\link{clusterGraph}}, to define clusters (i.e., communities) from the graph.
#'
#' \code{\link{clusterGraph.se}}, which builds an SNN graph from a \link[SingleCellExperiment]{SingleCellExperiment}.
#'
#' @examples
#' data <- matrix(rnorm(10000), ncol=1000)
#' out <- buildSnnGraph(data)
#' str(out)
#'
#' # We can use this to make an igraph::graph.
#' g <- igraph::make_undirected_graph(out$edges, n = out$vertices)
#' igraph::E(g)$weight <- out$weight
#'
#' @export 
#' @importFrom BiocNeighbors findKNN AnnoyParam
buildSnnGraph <- function(x, num.neighbors=10, weight.scheme="ranked", num.threads=1, BNPARAM=AnnoyParam(), as.pointer=FALSE) {
    .checkSEX(x, "clusterGraph.se")

    if (!is.list(x)) {
        x <- findKNN(x, k=num.neighbors, transposed=TRUE, get.index="transposed", get.distance=FALSE, num.threads=num.threads, BNPARAM=BNPARAM)
    } else {
        .checkNeighborResults(x$index, NULL)
    }

    out <- build_snn_graph(x$index, scheme=weight.scheme, num_threads=num.threads)
    if (!as.pointer) {
        out <- graph_to_list(out)
    }
    out
}
