# Diagnostic plots --------------------------------------------------------------

#' Histogram of observed values with true density overlay
#'
#' For censored samples, uses latent \code{t_true} values to overlay the
#' target density when available; observed \code{x} values are histogrammed
#' with a note on censoring status.
#'
#' @param samp a \code{censored_sample} object.
#' @param dist optional \code{dist_spec}; defaults to \code{samp$dist}.
#' @param use_latent logical; plot histogram of latent failures
#'   (\code{t_true}) instead of observed \code{x} (default \code{TRUE} for
#'   verifying the underlying distribution).
#' @param nbreaks number of histogram breaks (default \code{30}).
#' @param ... graphical parameters passed to \code{\link[graphics]{hist}}.
#' @return Invisibly, an object of class \code{"histogram"} as returned by
#'   \code{\link[graphics]{hist}}, containing break points, counts, and
#'   density values.  Called primarily for its side effect of producing a
#'   histogram plot with an optional PDF overlay.
#' @export
plot_censor_hist <- function(samp, dist = NULL, use_latent = TRUE,
                             nbreaks = 30L, ...) {
  if (!inherits(samp, "censored_sample")) {
    stop("'samp' must be a censored_sample object.", call. = FALSE)
  }
  dist <- dist %||% samp$dist
  vals <- if (use_latent) samp$t_true else samp$x
  ttl  <- paste0(samp$scheme, " - ", dist$name,
                 if (use_latent) " (latent)" else " (observed)")
  h <- graphics::hist(vals, breaks = nbreaks, freq = FALSE,
                      main = ttl, xlab = "Value", col = "steelblue",
                      border = "white", ...)
  if (!is.null(dist$d)) {
    xr <- range(vals, na.rm = TRUE)
    grid <- seq(xr[1], xr[2], length.out = 200)
    graphics::lines(grid, vapply(grid, function(t) dist$d(t, dist$param),
                                 numeric(1)), col = "darkred", lwd = 2)
    graphics::legend("topright", legend = c("Sample", "PDF"),
                     fill = c("steelblue", NA), border = c("white", NA),
                     lty = c(NA, 1), col = c(NA, "darkred"), lwd = c(NA, 2),
                     bty = "n")
  }
  invisible(h)
}

#' Dot plot (index plot) of sample values
#'
#' @param samp a \code{censored_sample} object.
#' @param use_latent logical; plot latent (\code{t_true}) or observed
#'   (\code{x}) values.
#' @param pch plotting character; censored points use \code{pch = 1},
#'   events use \code{pch = 16}.
#' @param ... passed to \code{\link[graphics]{plot}}.
#' @return Invisibly, a list with elements \code{index} (integer vector of
#'   observation indices) and \code{values} (numeric vector of plotted
#'   values).  Called primarily for its side effect of producing a dot
#'   (index) plot distinguishing events from censored observations.
#' @export
plot_censor_dot <- function(samp, use_latent = TRUE, pch = NULL, ...) {
  if (!inherits(samp, "censored_sample")) {
    stop("'samp' must be a censored_sample object.", call. = FALSE)
  }
  vals <- if (use_latent) samp$t_true else samp$x
  idx  <- seq_along(vals)
  ttl  <- paste0(samp$scheme, " dot plot - ", samp$dist$name)
  graphics::plot(idx, vals, type = "n", main = ttl,
                 xlab = "Index", ylab = "Value", ...)
  ev  <- samp$status == 1L
  cen <- !ev
  if (is.null(pch)) {
    graphics::points(idx[ev], vals[ev], pch = 16, col = "darkgreen")
    if (any(cen)) {
      graphics::points(idx[cen], vals[cen], pch = 1, col = "firebrick")
    }
    graphics::legend("topright",
                     legend = c("Event", "Censored"),
                     pch = c(16, 1), col = c("darkgreen", "firebrick"),
                     bty = "n")
  } else {
    graphics::points(idx, vals, pch = pch, ...)
  }
  invisible(list(index = idx, values = vals))
}

#' Autocorrelation function plot
#'
#' Plots the ACF of latent failure times to assess independence of the
#' underlying RNG stream (should resemble white noise for valid simulation).
#'
#' @param samp a \code{censored_sample} object.
#' @param use_latent logical; compute ACF on \code{t_true} (default) or
#'   observed \code{x}.
#' @param max_lag maximum lag (default \code{min(20, n/4)}).
#' @param ... passed to \code{\link[graphics]{plot}}.
#' @return Invisibly, an object of class \code{"acf"} as returned by
#'   \code{\link[stats]{acf}}, containing the autocorrelation values at each
#'   lag.  Called primarily for its side effect of producing an ACF plot
#'   with 95\% confidence bands.
#' @export
plot_censor_acf <- function(samp, use_latent = TRUE, max_lag = NULL, ...) {
  if (!inherits(samp, "censored_sample")) {
    stop("'samp' must be a censored_sample object.", call. = FALSE)
  }
  vals <- if (use_latent) samp$t_true else samp$x
  n    <- length(vals)
  if (is.null(max_lag)) max_lag <- min(20L, max(1L, n %/% 4L))
  acf_obj <- stats::acf(vals, lag.max = max_lag, plot = FALSE)
  ttl <- paste0(samp$scheme, " ACF - ", samp$dist$name)
  ci  <- stats::qnorm(0.975) / sqrt(n)
  graphics::plot(acf_obj$lag[-1], acf_obj$acf[-1], type = "h",
                 ylim = c(-1, 1), main = ttl, xlab = "Lag", ylab = "ACF",
                 ...)
  graphics::abline(h = c(-ci, ci), lty = 2, col = "gray50")
  graphics::abline(h = 0)
  invisible(acf_obj)
}

#' Combined diagnostic panel: histogram, dot plot, and ACF
#'
#' Produces a three-panel plot (histogram with PDF overlay, dot plot, and
#' ACF) for a censored sample.  Graphical parameters are saved before
#' modification and restored on exit via \code{\link{on.exit}}.
#'
#' @param samp a \code{censored_sample} object.
#' @param dist optional \code{dist_spec}.
#' @param use_latent passed to individual plot functions.
#' @param ... ignored.
#' @return Invisibly, the input \code{censored_sample} object \code{samp}.
#'   Called primarily for its side effect of producing a combined diagnostic
#'   plot panel.
#' @export
plot_censor_diagnostics <- function(samp, dist = NULL, use_latent = TRUE,
                                    ...) {
  if (!inherits(samp, "censored_sample")) {
    stop("'samp' must be a censored_sample object.", call. = FALSE)
  }
  oldpar <- graphics::par(no.readonly = TRUE)
  on.exit(graphics::par(oldpar))
  graphics::par(mfrow = c(1, 3), mar = c(4, 4, 3, 1))
  plot_censor_hist(samp, dist = dist, use_latent = use_latent)
  plot_censor_dot(samp, use_latent = use_latent)
  plot_censor_acf(samp, use_latent = use_latent)
  invisible(samp)
}

#' @keywords internal
`%||%` <- function(x, y) if (is.null(x)) y else x
