#' @name L1centEDGE
#' @title Multiscale Edge Representation
#'
#' @description
#' Derives a multiscale edge representation. Each vertex is connected to its
#' local median, which is found in its
#' \ifelse{html}{\out{<i>L</i><sub>1</sub>}}{{\eqn{L_1}}} centrality-based
#' neighborhood.
#'
#' @details
#' In a global perspective, any given undirected graph can be represented as a
#' star-shaped directed graph, with each vertex making a connection to the
#' median vertex. Based on this idea, an undirected graph can be represented as
#' a directed graph, with each vertex making a connection to the \emph{local}
#' median vertex. The local median vertex of, say, \eqn{v_i}, is defined as a
#' median vertex among the
#' \ifelse{html}{\out{<i>L</i><sub>1</sub>}}{{\eqn{L_1}}} centrality-based
#' neighborhood of \eqn{v_i}. By varying the level of locality, the given graph
#' can be visually inspected at multiple scales. Refer to Kang and Oh (2026) for
#' details.
#'
#' @note
#' The function is valid only for undirected and connected graphs.
#'
#' @inheritParams L1centLOC
#' @param g An \code{igraph} graph object or a distance matrix.
#' * When \code{g} is an \code{igraph} object, the graph must be undirected and connected.
#' * When \code{g} is a matrix, it should be symmetric and represent a distance matrix.
#' All entries of the distance matrix must be finite.
#' @return \code{L1centEDGE()} returns an object of class \code{L1centEDGE}. It
#'   is a list of \sQuote{edge lists}. The length of the list is equivalent to
#'   the length of \code{alpha}, and the names of the list are the values of
#'   \code{alpha}. The \code{i}th component of the list is a 2-column matrix,
#'   and each row defines one directed edge, i.e., it is an edge list. The
#'   second column is the local (level \code{alpha[i]}) median of the vertex at
#'   the first column. There may be more than one edge from each vertex, since
#'   there may be more than one local median.
#'
#'  `print.L1centEDGE()` prints the edge lists and returns them invisibly.
#'
#' @export
#' @seealso [L1cent()], [L1centNB()], [L1centLOC()].
#' @examples
#' library(igraph)
#' MCU_edge <- L1centEDGE(MCUmovie, vertex_weight = V(MCUmovie)$worldwidegross, alpha = 5/32)
#' plot(MCU_edge)
#' @references S. Kang and H.-S. Oh. On a notion of graph centrality based on
#'   \ifelse{html}{\out{<i>L</i><sub>1</sub>}}{{\eqn{L_1}}} data depth.
#'   \emph{Journal of the American Statistical Association}, 121(553): 400--412, 2026.
L1centEDGE <- function(g, vertex_weight, alpha, edge_weight_transform) UseMethod("L1centEDGE")

#' @name L1centEDGE
#' @exportS3Method L1centEDGE igraph
L1centEDGE.igraph <- function(g, vertex_weight=NULL, alpha, edge_weight_transform = NULL){
  validate_igraph(g)

  new_weight <- edge_weight_transform0(g, edge_weight_transform)
  if(!is.null(new_weight)) igraph::E(g)$weight <- new_weight
  D <- igraph::distances(g)
  attr(D, "label.igraph") <- igraph::V(g)$label
  attr(D, "color.igraph") <- igraph::V(g)$color
  L1centEDGE.matrix(D, vertex_weight=vertex_weight, alpha)
}

#' @name L1centEDGE
#' @exportS3Method L1centEDGE matrix
L1centEDGE.matrix <- function(g, vertex_weight=NULL, alpha, edge_weight_transform = NULL){
  eta <- vertex_weight
  if(is.null(eta)) eta <- rep(1,ncol(g))
  validate_matrix(g, eta)
  if(!all(alpha >= 0 & alpha <= 1))
    stop("alpha is not in the correct range: [0,1]")

  calls <- sys.calls()
  fnames <- sapply(calls, function(cl) as.character(cl[[1]]))
  # print(fnames)
  if(all(fnames[1:2] == c("L1centEDGE", "L1centEDGE.matrix")))
    message("DISTANCE matrix received")

  n <- ncol(g)
  label <- colnames(g)
  if(is.null(label))
    label <- 1:n

  if(identical(alpha, 1) | identical(alpha, 1L)){
    glob.median <- which(L1cent(g, eta) == 1)
    glob.median.length <- length(glob.median)
    glob.edgelist <- cbind(rep(1:n,each=glob.median.length),rep(glob.median,times=glob.median.length))
    rownames(glob.edgelist) <- NULL
    glob.edgelist <- matrix(label[glob.edgelist],ncol=2)
    glob.edgelist <- list(glob.edgelist)
    names(glob.edgelist) <- alpha
    return(structure(glob.edgelist,
                     class = "L1centEDGE",
                     alpha = alpha,
                     label = label,
                     label.igraph = attr(g,"label.igraph"),
                     color.igraph = attr(g,"color.igraph")))
  }

  # m <- ceiling(n*alpha)
  NB <- L1centNB(g)
  edgelist <- vector("list", length = length(alpha))
  names(edgelist) <- alpha

  for (i in seq_along(alpha)) {
    nb.index <-
      lapply(NB, function(l)
        which(l >= stats::quantile(l, 1 - alpha[i], type = 1)))
    loc.median <-
      lapply(1:length(nb.index), function(j){
        distsum <- colSums(g[nb.index[[j]],nb.index[[j]],drop=FALSE] * eta[nb.index[[j]]])
        nb.index[[j]][which(distsum == min(distsum))]})
    loc.median.length <- sapply(loc.median, length)
    edgelist[[i]] <- cbind(rep(1:n, times = loc.median.length), unlist(loc.median))
    rownames(edgelist[[i]]) <- NULL
    edgelist[[i]] <- matrix(label[edgelist[[i]]],ncol=2)
  }
  return(structure(edgelist,
                   class = "L1centEDGE",
                   alpha = alpha,
                   label = label,
                   label.igraph = attr(g,"label.igraph"),
                   color.igraph = attr(g,"color.igraph")))
}

#' @name L1centEDGE
#' @aliases print.L1centEDGE
#'
#' @param x An \code{L1centEDGE} object, obtained as a result of the function
#'   \code{L1centEDGE()}.
#' @param ... Further arguments passed to or from other methods.
#'   * `plot()` method: Further graphical parameters supplied to the internal
#'   [igraph::plot.igraph()] function. See [igraph::plot.common] document.
#'   * `print()` and `summary()` methods: Further arguments passed to the
#'   [base::print()] and [base::summary()] functions, respectively.
#' @export
print.L1centEDGE <- function(x, ...){
  for(i in seq_along(x)){
    cat("edge list at alpha = ",
        round(attr(x, "alpha")[[i]], 4), ":\n", sep = "")
    print.default(x[[i]], ...)
    if(i != length(x)) cat("\n")
  }
  return(invisible(x))
}


#' @name L1centEDGE
#' @aliases plot.L1centEDGE
#'
#' @return `plot.L1centEDGE()` draws directed graphs corresponding to each value
#'   of \code{alpha}. In each display, each vertex gives a directed edge to its
#'   local median vertex. The local median vertices are shown with larger
#'   circles.
#' @export
plot.L1centEDGE <- function(x, ...){
  args <- list(...)
  args.orignal <- args
  for(i in seq_along(x)){
    x[[i]] <- x[[i]][x[[i]][,1] != x[[i]][,2],]
    gi <- igraph::graph_from_edgelist(x[[i]])

    if(is.null(args.orignal$main)) args$main <- paste0("alpha = ", attr(x,"alpha")[i])
    if(is.null(args.orignal$edge.arrow.size)) args$edge.arrow.size <- 0.5
    if(is.null(args.orignal$vertex.size)) args$vertex.size <- sapply(if(is.null(igraph::V(gi)$name)) as.numeric(igraph::V(gi)) else igraph::V(gi)$name, function(elem) ifelse(elem %in% x[[i]][,2], 25, 15))
    if(is.null(args.orignal$vertex.label.family)) args$vertex.label.family <- "sans"
    if(is.null(args.orignal$vertex.label.cex)) args$vertex.label.cex <- 1
    if(is.null(args.orignal$vertex.label)) args$vertex.label <- if(is.null(attr(x, "label.igraph"))) igraph::V(gi)$name else attr(x, "label.igraph")[match(igraph::V(gi)$name, attr(x, "label"))]
    if(is.null(args.orignal$vertex.color)) args$vertex.color <- if(is.null(attr(x, "color.igraph"))) "gray" else attr(x, "color.igraph")[match(igraph::V(gi)$name, attr(x, "label"))]

    if(i > 1) withr::local_par(ask = TRUE)
    do.call(igraph::plot.igraph, c(list(gi), args))
  }
}
