#' Plot validation metrics across candidate numbers of clusters
#'
#' @description
#' Produces a multi-panel line plot displaying one or more clustering
#' validation metrics as a function of the number of clusters \code{k},
#' as computed by \code{\link{validate_clustering}} and optionally extended
#' with a \code{score} column by \code{\link{choose_best_clustering}}. For
#' each metric, the optimal value is highlighted in red.
#'
#' @param validation_table A \code{data.frame} as returned by
#'   \code{\link{validate_clustering}} or \code{$scores_table} from
#'   \code{\link{choose_best_clustering}}. Must contain a column \code{k}
#'   and at least one of the requested metric columns.
#' @param metrics A character vector specifying which metrics to plot.
#'   Only metrics present in \code{validation_table} are plotted; others
#'   are silently ignored. Default includes all standard metrics plus
#'   \code{score}:
#'   \code{"pac"}, \code{"silhouette_mean"},
#'   \code{"ari_mean_between_imputations"}, \code{"ari_consensus_mean"},
#'   \code{"calinski_harabasz_mean"}, \code{"davies_bouldin_mean"},
#'   \code{"dunn_index"}, and \code{"score"}.
#'
#' @return Invisibly returns \code{NULL}. The function is called for its
#'   side effect of producing a plot.
#'
#' @details
#' The number of panels is determined automatically from the number of
#' available metrics using \code{ceiling(sqrt(n))} rows and
#' \code{ceiling(n / nrow)} columns, producing a layout as square as
#' possible.
#'
#' The direction of optimality is accounted for when highlighting the best
#' value: lower is better for \code{pac}, \code{davies_bouldin_mean}, and
#' \code{score}; higher is better for all remaining metrics. The optimal
#' point for each metric is highlighted in red.
#'
#' Original graphical parameters are restored on exit via
#' \code{\link[graphics]{par}}.
#'
#' When \code{score} is present (i.e., \code{validation_table} comes from
#' \code{\link{choose_best_clustering}}), the panel for \code{score} shows
#' the overall weighted rank aggregation - the \code{k} with the lowest
#' score is the recommended solution.
#'
#' @examples
#' # ------------------------------------------------------------
#' # Example 1: Validation metrics with simulated data
#' # ------------------------------------------------------------
#' val_table <- data.frame(
#'   k                            = 2:4,
#'   pac                          = c(0.15, 0.08, 0.20),
#'   silhouette_mean              = c(0.42, 0.61, 0.38),
#'   ari_mean_between_imputations = c(0.80, 0.91, 0.75),
#'   ari_consensus_mean           = c(0.82, 0.93, 0.77),
#'   calinski_harabasz_mean       = c(120,  198,  105),
#'   davies_bouldin_mean          = c(0.85, 0.54, 0.97),
#'   dunn_index                   = c(0.30, 0.48, 0.27)
#' )
#'
#' # Plot all metrics
#' plot_validation_metrics(val_table)
#'
#' # Plot only stability metrics
#' plot_validation_metrics(val_table,
#'                         metrics = c("pac",
#'                                     "ari_mean_between_imputations",
#'                                     "ari_consensus_mean"))
#' \donttest{
#' # ------------------------------------------------------------
#' # Example 2: metrics validation with mice
#' # ------------------------------------------------------------
#'
#' if (requireNamespace("mice", quietly = TRUE)) {
#'
#' set.seed(123)
#' imp    <- mice::mice(mice::nhanes, m = 5, printFlag = FALSE)
#' mild   <- as_mild_list(imp)
#' parts  <- cluster_imputations(mild, method = "ward.D2", k = 2:5)
#' cons   <- consensus_clustering(parts)
#' val    <- validate_clustering(parts, cons)
#'
#' # Plot all available metrics (no score column yet)
#' plot_validation_metrics(val)
#'
#' # Include score column from choose_best_clustering
#' best <- choose_best_clustering(val, cons)
#' plot_validation_metrics(best)
#'
#' # Plot only stability metrics
#' plot_validation_metrics(val,
#'                         metrics = c("pac",
#'                                     "ari_mean_between_imputations",
#'                                     "ari_consensus_mean"))
#' }
#' }
#'
#' @seealso
#' \code{\link{validate_clustering}}, \code{\link{choose_best_clustering}},
#' \code{\link{plot_consensus_dendrogram}}, \code{\link{plot_consensus_matrix}}
#'
#' @export
plot_validation_metrics <- function(validation_table,
                                    metrics = c("pac",
                                                "silhouette_mean",
                                                "ari_mean_between_imputations",
                                                "ari_consensus_mean",
                                                "calinski_harabasz_mean",
                                                "davies_bouldin_mean",
                                                "dunn_index",
                                                "score")) {

  # Auto-extract scores_table from mi_clustering_result or choose_best_clustering output
  if (inherits(validation_table, "mi_clustering_result")) {
    validation_table <- validation_table$scores_table

  } else if (is.list(validation_table) && !is.data.frame(validation_table)) {

    if (!is.null(validation_table$scores_table)) {
      validation_table <- validation_table$scores_table
    } else {
      stop("Could not extract a valid scores_table from the provided list.")
    }
  }

  if (!is.data.frame(validation_table)) {
    stop("validation_table must be a data.frame.")
  }

  if (!("k" %in% names(validation_table))) {
    stop("validation_table must contain column 'k'.")
  }

  available_metrics <- intersect(metrics, names(validation_table))

  if (length(available_metrics) == 0) {
    stop("None of the requested metrics are available in validation_table.")
  }

  old_par <- graphics::par(no.readonly = TRUE)
  on.exit(graphics::par(old_par))

  n         <- length(available_metrics)
  nrow_plot <- ceiling(sqrt(n))
  ncol_plot <- ceiling(n / nrow_plot)

  graphics::par(mfrow = c(nrow_plot, ncol_plot))

  # labels for each metric
  metric_labels <- c(
    pac                          = "PAC",
    silhouette_mean              = "Silhouette",
    ari_mean_between_imputations = "ARI (between imputations)",
    ari_consensus_mean           = "ARI (consensus)",
    calinski_harabasz_mean       = "Calinski-Harabasz",
    davies_bouldin_mean          = "Davies-Bouldin",
    dunn_index                   = "Dunn Index",
    score                        = "Weighted Score"
  )

  for (metric in available_metrics) {

    y <- validation_table[[metric]]

    label <- if (!is.na(metric_labels[metric])) metric_labels[metric] else gsub("_", " ", metric)

    plot(validation_table$k, y,
         type = "b",
         pch  = 19,
         xlab = "Number of clusters (k)",
         ylab = label,
         main = label,
         xaxt = "n")

    graphics::axis(1, at = validation_table$k)

    best_idx <- which.max(
      if (metric %in% c("pac", "davies_bouldin_mean", "score")) -y else y
    )
    if (length(best_idx) == 0) next

    graphics::points(validation_table$k[best_idx], y[best_idx],
           pch = 19, cex = 1.4, col = "red")
  }
  invisible(NULL)
}
