#' Plot MDS
#'
#' Plot multi-dimensional scaling plot using algorithm of limma::plotMDS(). It is recommended this be done with the
#' log-methylation-ratio matrix generated by bsseq_to_log_methy_ratio().
#'
#' @param x a numeric matrix of log-methylation-ratio values where rows are genomic regions/features and columns
#'   are samples.
#' @param top the number of top variable features (genes/regions) used to calculate
#'   pairwise distances between samples.
#' @param plot_dims the numeric vector of the two dimensions to be plotted
#'   (default c(1, 2) for first two principal coordinates).
#' @param labels the character vector of labels for data points. By default uses
#'   column names of x, set to NULL to plot unlabeled points.
#' @param groups the character vector or numeric vector of groups that data points
#'   will be coloured by. For character vectors, discrete colours are used. For
#'   numeric vectors, continuous colour scale is used. By default, groups is NULL
#'   and points are not coloured.
#' @param legend_name the name for the colour legend.
#'
#' @return ggplot object of the MDS plot.
#'
#' @details
#' Multi-dimensional scaling (MDS) is a dimensionality reduction technique that
#' represents high-dimensional distances between samples in a lower-dimensional
#' space (typically 2D). This function uses limma's plotMDS algorithm, which
#' selects the most variable features and calculates leading log-fold-change
#' distances between samples. The resulting plot shows samples as points where
#' proximity indicates similarity in methylation profiles. When variance explained
#' is available, it is shown as percentages on the axis labels.
#'
#' @examples
#' nmr <- load_example_nanomethresult()
#' bss <- methy_to_bsseq(nmr)
#' lmr <- bsseq_to_log_methy_ratio(bss)
#' plot_mds(lmr)
#'
#' @importFrom limma plotMDS
#' @importFrom ggplot2 draw_key_point
#' @export
plot_mds <- function(x, top = 500, plot_dims = c(1, 2), labels = colnames(x), groups = NULL, legend_name = "group") {
    if (!is.null(labels)) {
        assertthat::assert_that(ncol(x) == length(labels))
    }

    if (!is.null(groups)) {
        assertthat::assert_that(ncol(x) == length(groups))
    }

    mds_res <- limma::plotMDS(
        x,
        top = top,
        plot = FALSE
    )

    var_exp1 <- round(100 * mds_res$var.explained[plot_dims[1]])
    var_exp2 <- round(100 * mds_res$var.explained[plot_dims[2]])

    if ("eigen.vectors" %in% names(mds_res)) {
        plot_data <- data.frame(
            dim1 = mds_res$eigen.vectors[, plot_dims[1]],
            dim2 = mds_res$eigen.vectors[, plot_dims[2]]
        )
        xlabel <- glue::glue("Leading logFC Dim {plot_dims[1]} ({var_exp1}%)")
        ylabel <- glue::glue("Leading logFC Dim {plot_dims[2]} ({var_exp2}%)")
    } else {
        plot_data <- data.frame(
            dim1 = mds_res$cmdscale.out[, plot_dims[1]],
            dim2 = mds_res$cmdscale.out[, plot_dims[2]]
        )
        xlabel <- glue::glue("Leading logFC Dim {plot_dims[1]}")
        ylabel <- glue::glue("Leading logFC Dim {plot_dims[2]}")
    }

    plot_data$labels <- labels
    plot_data$group <- groups

    p <- ggplot2::ggplot(
        plot_data,
        ggplot2::aes(x = .data$dim1, y = .data$dim2)
    )

    if (is.null(groups)) {
        if (is.null(labels)) {
            # no labels or groups
            p <- p + ggplot2::geom_point()
        } else {
            # no groups, but labels
            p <- p + ggplot2::geom_label(aes(label = labels))
        }
    } else {
        if (is.numeric(groups)) {
            # continuous colour palette ignores labels
            if (!is.null(labels)) {
                message("Ignoring labels as groups is numeric. Set `labels=NULL` to suppress this message.")
            }
            p <- p +
                ggplot2::geom_point(aes(colour = .data$group)) +
                ggplot2::scale_color_continuous(name = legend_name)
        } else {
            # discrete colour palette
            if (is.null(labels)) {
                # no labels, but groups
                p <- p + ggplot2::geom_point(aes(colour = .data$group)) +
                    ggplot2::guides(color = ggplot2::guide_legend(title = legend_name))
            } else {
                # labels and groups
                # key_glyph causes the legend to display points
                p <- p + ggplot2::geom_label(
                    aes(label = labels, colour = .data$group),
                    key_glyph = ggplot2::draw_key_point
                )
            }
        }
    }

    p +
        ggplot2::theme_bw() +
        ggplot2::xlab(xlabel) +
        ggplot2::ylab(ylabel) +
        ggplot2::scale_x_continuous(expand = c(.1, .1))
}
