# ============================================================
# Algorithm 1 -- Uniform node subsampling for multivariate
#                network moments.
# ============================================================


#' Uniform node subsampling for multivariate network moments (Algorithm 1)
#'
#' Repeatedly draws random node-induced subgraphs of size \code{b} from
#' network \code{G} and computes network moments for each subsample.  The
#' resulting empirical distribution approximates the joint distribution of
#' network moments for networks of size \code{b} drawn from the same
#' underlying graphon model, providing the foundation for downstream inference.
#'
#' @param G A square symmetric binary adjacency matrix (dense \code{matrix}
#'   or sparse \code{Matrix}) of size \eqn{n \times n}.
#' @param b Integer; the subsampling size.  Must satisfy \eqn{3 \le b < n}.
#' @param N_sub Integer; number of subsamples to draw.
#' @param motifs A list of motif specifications.  Each element is either:
#'   \itemize{
#'     \item a character string: \code{"v_shape"}, \code{"triangle"}, or
#'       \code{"star3"} (built-in defaults); or
#'     \item a named list with elements \code{fn} -- a
#'       \code{function(A)} returning a \emph{named} numeric vector -- and
#'       \code{n_edges} -- an integer vector of edge counts matching the
#'       output length of \code{fn}.
#'   }
#'   Defaults to \code{list("v_shape", "triangle", "star3")}.
#' @param parallel Logical; if \code{TRUE} the subsampling loop is run in
#'   parallel via \pkg{doParallel} and \pkg{foreach}.  Default \code{FALSE}.
#'   Requires the \pkg{netsubsamp} package to be \emph{installed} on all
#'   worker nodes when \code{TRUE}.
#' @param n_cores Integer or \code{NULL}; number of cores to use when
#'   \code{parallel = TRUE}.  If \code{NULL}, uses
#'   \code{parallel::detectCores() - 1} (minimum 1).
#'
#' @return A list with:
#' \describe{
#'   \item{\code{rho}}{Estimated edge density of \code{G}.}
#'   \item{\code{moments}}{An \eqn{N_{\mathrm{sub}} \times m} numeric matrix
#'     of subsampled network moments.  Rows for which the subsampled graph has
#'     zero density contain \code{NA}.}
#'   \item{\code{motif_names}}{Character vector of moment names, in column
#'     order of \code{moments}.}
#'   \item{\code{b}}{The subsampling size used.}
#' }
#'
#' @references
#' Qi, Hua, Li and Zhou (2024). Multivariate Inference of Network Moments by
#' Subsampling. \emph{Biometrika}, \strong{111}(1), 1--18.
#' \doi{10.1093/biomet/asad056}.
#'
#' @examples
#' \donttest{
#' library(randnet)
#' set.seed(1)
#' G   <- BlockModel.Gen(lambda = 15, n = 200, K = 3)$A
#' res <- subsample_moments(G, b = 60, N_sub = 500)
#' head(res$moments)
#' res$rho
#' }
#'
#' @importFrom foreach foreach "%dopar%"
#' @export
subsample_moments <- function(
    G,
    b,
    N_sub,
    motifs   = list("v_shape", "triangle", "star3"),
    parallel = FALSE,
    n_cores  = NULL
) {
  validate_adjacency(G, "G")
  n     <- nrow(G)
  b     <- as.integer(b)
  N_sub <- as.integer(N_sub)

  if (b >= n)   stop("'b' must be strictly less than nrow(G).")
  if (b < 3L)   stop("'b' must be at least 3.")
  if (N_sub < 1L) stop("'N_sub' must be a positive integer.")

  motif_specs <- init_motif_specs(motifs)
  m           <- length(motif_specs$names)

  par_setup <- setup_foreach(parallel, n_cores)
  on.exit(par_setup$cleanup(), add = TRUE)
  `%op%` <- par_setup$op

  # Freeze all variables needed inside the loop to avoid accidental
  # re-evaluation or masking in the parallel environment.
  G_local      <- G
  b_local      <- b
  specs_local  <- motif_specs

  pkg_arg <- if (parallel) "netsubsamp" else character(0L)

  result_list <- foreach::foreach(
    i         = seq_len(N_sub),
    .packages = pkg_arg,
    .export   = c("compute_network_moments", "init_motif_specs")
  ) %op% {
    n_w    <- nrow(G_local)
    nodes  <- sample(n_w, b_local, replace = FALSE)
    G_sub  <- as.matrix(G_local[nodes, nodes])

    stats  <- compute_network_moments(G_sub, specs_local)

    if (stats$rho < 1e-9) {
      rep(NA_real_, length(specs_local$names))
    } else {
      stats$UR
    }
  }

  moments_mat <- do.call(rbind, result_list)
  rownames(moments_mat) <- NULL
  colnames(moments_mat) <- motif_specs$names

  rho_G <- sum(G) / (n * (n - 1L))

  list(
    rho         = rho_G,
    moments     = moments_mat,
    motif_names = motif_specs$names,
    b           = b
  )
}
