# ============================================================
# Algorithm 2 -- Externally sparsified network moments.
# ============================================================


# ------------------------------------------------------------
# Internal workhorse (called from both the public wrapper and
# the Algorithm 3 subsampling loop).
# ------------------------------------------------------------

#' Compute externally sparsified network moments (internal)
#'
#' Uses \code{G1} to estimate the sparsification probability and
#' independently applies random edge removal to \code{G2} to reach the
#' target density \code{rho_target}.  Returns the density-normalised moment
#' vector \eqn{\bar\Psi_{\rho^\dagger}(G_1, G_2)}.
#'
#' @param G1 Dense binary adjacency matrix used as the density estimator.
#' @param G2 Dense binary adjacency matrix used as the moment source.
#' @param rho_target Numeric; target edge density \eqn{\rho^\dagger \in (0,1)}.
#' @param motif_specs Compiled motif specification from
#'   \code{\link{init_motif_specs}}.
#'
#' @return Named numeric vector of density-normalised moments
#'   \eqn{\hat\rho_{\tilde{G}_2}^{-|E(R)|} \cdot U_R(\tilde{G}_2)}, or a
#'   vector of \code{NA} values if the result is degenerate (zero density
#'   after edge removal).
#'
#' @importFrom stats setNames
#' @keywords internal
.sparsify_moments_internal <- function(G1, G2, rho_target, motif_specs) {

  m      <- length(motif_specs$names)
  na_vec <- stats::setNames(rep(NA_real_, m), motif_specs$names)

  k1      <- nrow(G1)
  rho_G1  <- sum(G1) / (k1 * (k1 - 1L))

  if (rho_G1 < 1e-9) return(na_vec)

  # Sparsification probability: retain each edge in G2 with this probability
  # so that G2's density is driven towards rho_target.
  p_hat <- min(1, rho_target / rho_G1)

  G2_sparsified <- sparsify_graph(G2, p_hat)
  stats         <- compute_network_moments(G2_sparsified, motif_specs)

  if (stats$rho < 1e-9) return(na_vec)

  # Density normalisation: Psi-bar = rho^{-|E(R)|} * U_R
  stats$rho^(-motif_specs$edge_counts) * stats$UR
}


# ------------------------------------------------------------
# Public wrapper
# ------------------------------------------------------------

#' Externally sparsified network moments (Algorithm 2)
#'
#' Uses one adjacency matrix (\code{G1}) to estimate the sparsification
#' probability and a second independent adjacency matrix (\code{G2}) to
#' compute density-normalised network moments after random edge removal to a
#' common target density.  The resulting statistic
#' \eqn{\bar\Psi_{\rho^\dagger}(G_1, G_2)} is the building block for the
#' two-sample test in \code{\link{two_sample_test}}.
#'
#' @section Algorithm:
#' \enumerate{
#'   \item Estimate \eqn{\hat{p} = \min\!\left(1,\,\rho^\dagger /
#'     \hat\rho_{G_1}\right)}.
#'   \item Randomly remove edges from \eqn{G_2}: each edge is retained
#'     independently with probability \eqn{\hat{p}}, yielding
#'     \eqn{\tilde{G}_2}.
#'   \item Return \eqn{\hat\rho_{\tilde{G}_2}^{-|E(R)|} \cdot
#'     U_R(\tilde{G}_2)} for each motif \eqn{R}.
#' }
#'
#' @param G1 A square symmetric binary adjacency matrix (dense or sparse
#'   \code{Matrix}) used to estimate the sparsification probability.
#' @param G2 A square symmetric binary adjacency matrix from which moments
#'   are computed after edge removal.
#' @param rho_target Numeric in \eqn{(0, 1)}; the target edge density
#'   \eqn{\rho^\dagger}.
#' @param motifs A list of motif specifications; see
#'   \code{\link{subsample_moments}} for the format.  Defaults to
#'   \code{list("v_shape", "triangle", "star3")}.
#'
#' @return A named numeric vector of density-normalised network moments, or
#'   \code{NA} values if the graph after edge removal has zero density.
#'
#' @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(2)
#' G1 <- BlockModel.Gen(lambda = 15, n = 100, K = 3)$A
#' G2 <- BlockModel.Gen(lambda = 15, n = 100, K = 3)$A
#' sparsify_moments(G1, G2, rho_target = 0.08)
#' }
#'
#' @export
sparsify_moments <- function(
    G1,
    G2,
    rho_target,
    motifs = list("v_shape", "triangle", "star3")
) {
  validate_adjacency(G1, "G1")
  validate_adjacency(G2, "G2")

  if (!is.numeric(rho_target) || length(rho_target) != 1L ||
      rho_target <= 0 || rho_target >= 1) {
    stop("'rho_target' must be a single numeric value strictly in (0, 1).")
  }

  motif_specs <- init_motif_specs(motifs)

  .sparsify_moments_internal(
    as.matrix(G1), as.matrix(G2), rho_target, motif_specs
  )
}
