#' Compare KEGG-Based Networks via Jaccard Similarity
#'
#' Computes pairwise Jaccard similarity between two or more KEGG-derived
#' networks, either at the node level (unique node sets) or edge level
#' (directed source–target pairs, optionally including interaction type).
#'
#' @param ... Two or more network matrices, typically generated by
#'   \code{buildKeggNetwork()}. Each must have at least two columns:
#'   \code{source} (column 1) and \code{target} (column 2). When
#'   \code{match_interaction = TRUE}, a third column
#'   \code{interaction_type} is required.
#' @param type Character, \code{"edge"} or \code{"node"}. If \code{"node"},
#'   similarity is computed on the set of unique nodes (union of sources
#'   and targets). If \code{"edge"}, similarity is computed on directed
#'   edges (source \eqn{\rightarrow} target).
#' @param match_interaction Logical. When \code{type = "edge"} and
#'   \code{match_interaction = FALSE} (default), edges are defined by
#'   \code{source -> target} only. If \code{TRUE}, edges must also share
#'   the same \code{interaction_type} to be considered identical. Ignored
#'   when \code{type = "node"}.
#'
#' @return
#' A numeric similarity matrix with dimensions
#' \code{length(list(...)) x length(list(...))}. Row and column names are
#' derived from the argument names in \code{...}. Diagonal entries are 1;
#' off-diagonal entries are Jaccard indices,
#' \deqn{J = |A \cap B| / |A \cup B|,}
#' where \eqn{A} and \eqn{B} are node sets (for \code{type = "node"}) or
#' edge sets (for \code{type = "edge"}). If both sets are empty, the
#' similarity is \code{NA_real_}.
#'
#' @details
#' This function is particularly useful for comparing pathways across
#' organisms or conditions using networks built with \code{buildKeggNetwork()}.
#'
#' @examples
#' ## 1) Prepare example network subsets
#' data(MetaPathNet_example_network)
#'
#' net_example_1 <- MetaPathNet_example_network[seq_len(50), , drop = FALSE]
#' net_example_2 <- MetaPathNet_example_network[51:100, , drop = FALSE]
#' net_example_3 <- MetaPathNet_example_network[101:150, , drop = FALSE]
#' net_example_4 <- MetaPathNet_example_network[151:200, , drop = FALSE]
#' net_example_5 <- MetaPathNet_example_network[201:250, , drop = FALSE]
#'
#' ## 2) Edge-level similarity (Jaccard on directed edges, ignoring interaction type)
#' ##    This compares overlap in network structure (source-target pairs).
#' sim_edge_example <- networkSimilarity(
#'   net_example_1,
#'   net_example_2,
#'   net_example_3,
#'   net_example_4,
#'   net_example_5,
#'   type              = "edge",
#'   match_interaction = FALSE
#' )
#'
#' ## 3) (Optional) Node-level similarity (Jaccard on unique node sets)
#' ##     This compares overlap in node composition, regardless of how nodes are connected.
#' sim_node_example <- networkSimilarity(
#'   net_example_1,
#'   net_example_2,
#'   net_example_3,
#'   net_example_4,
#'   net_example_5,
#'   type              = "node",
#'   match_interaction = FALSE
#' )
#'
#' @seealso
#' \code{\link{buildKeggNetwork}} for network construction,
#' \code{\link{getPathIDs}} for pathway ID retrieval.
#'
#' @references
#' Fuxman Bass JI, Diallo A, Nelson J, Soto JM, Myers CL, Walhout AJ.
#' Using networks to measure similarity between genes: association index selection.
#' Nat Methods. 2013;10(12):1169–1176. doi:10.1038/nmeth.2728.
#'
#' @export
networkSimilarity <- function(
    ...,
    type = c("edge", "node"),
    match_interaction = FALSE
) {
  #==============================================================
  # Step 1 — Collect and validate input network matrices
  #==============================================================
  nets <- list(...)
  if (length(nets) < 2L) {
    stop("At least two network matrices must be provided.")
  }

  type <- match.arg(type)

  # Derive argument names (for row/colnames of the similarity matrix)
  dots <- substitute(list(...))[-1L]
  arg_names <- vapply(dots, function(x) paste(deparse(x), collapse = ""), character(1))

  if (is.null(names(nets)) || any(names(nets) == "")) {
    names(nets) <- arg_names
  }

  #==============================================================
  # Step 2 — Convert each network to a set of elements
  #           type = 'edge' -> edge keys
  #           type = 'node' -> node IDs
  #==============================================================
  # Convert each network independently to edge keys or node IDs
  nets <- lapply(nets, function(net) {

    # Validate the minimum source–target network structure
    if (is.null(dim(net)) || ncol(net) < 2L) {
      stop(
        "All inputs must be matrices or data.frames with at least two columns ",
        "(source, target). When match_interaction = TRUE, at least three ",
        "columns are required: source, target, interaction_type."
      )
    }

    src <- as.character(net[, 1])
    tgt <- as.character(net[, 2])

    if (type == "edge") {
      if (match_interaction) {
        # Interaction-aware edge comparison requires a third column
        if (ncol(net) < 3L) {
          stop(
            "match_interaction = TRUE requires at least three columns: ",
            "source, target, interaction_type."
          )
        }
        int <- as.character(net[, 3])
        # Include interaction type in each unique edge key
        unique(paste(src, tgt, int, sep = "|"))
      } else {
        # Compare unique source–target edges without interaction type
        unique(paste(src, tgt, sep = "->"))
      }
    } else {
      # Node-level similarity uses all unique source and target nodes
      unique(c(src, tgt))
    }
  })

  #==============================================================
  # Step 3 — Initialize similarity matrix
  #==============================================================
  n <- length(nets)
  sim_mat <- matrix(
    NA_real_,
    nrow     = n,
    ncol     = n,
    dimnames = list(names(nets), names(nets))
  )
  diag(sim_mat) <- 1

  #==============================================================
  # Step 4 — Compute pairwise Jaccard similarities
  #==============================================================
  for (i in seq_len(n)) {
    if (i < n) {
      for (j in (i + 1L):n) {
        union_ij <- base::union(nets[[i]], nets[[j]])
        if (length(union_ij) == 0L) {
          value <- NA_real_   # both sets empty
        } else {
          inter_ij <- intersect(nets[[i]], nets[[j]])
          value <- length(inter_ij) / length(union_ij)
        }
        sim_mat[i, j] <- value
        sim_mat[j, i] <- value
      }
    }
  }

  sim_mat
}
