#' Create a Distribution Specification for Process Capability Analysis
#'
#' Constructs an S3 object of class \code{"pci_dist"} that encapsulates the
#' probability density function (PDF) and cumulative distribution function (CDF)
#' of a quality characteristic. This object is passed to \code{\link{pci}},
#' \code{\link{pci_ci}}, and \code{\link{pci_grid}} when computing generalised
#' indices such as \eqn{C_{py}}.
#'
#' @param pdf A function \code{function(x, ...)} returning the density at
#'   \code{x}. Additional distribution parameters are passed via \code{params}.
#' @param cdf A function \code{function(x, ...)} returning the cumulative
#'   probability \eqn{P(Z \le x)}. If \code{NULL}, a numerical CDF is derived
#'   via \code{\link[stats]{integrate}} over \code{pdf}.
#' @param params A named list of distribution parameters forwarded to
#'   \code{pdf} and \code{cdf} via \code{\link{do.call}}.
#' @param support A length-2 numeric vector giving the lower and upper bounds
#'   of the distribution's support (default \code{c(-Inf, Inf)}).
#'
#' @return An S3 object of class \code{"pci_dist"} with elements:
#'   \item{pdf_fn}{Closure that evaluates the PDF at a given point.}
#'   \item{cdf_fn}{Closure that evaluates the CDF at a given point.}
#'   \item{params}{The supplied parameter list.}
#'   \item{support}{The supplied support bounds.}
#'   \item{cdf_is_numeric}{Logical; \code{TRUE} if CDF was derived numerically.}
#'
#' @examples
#' # Normal distribution
#' d <- pci_dist(
#'   pdf = function(x, mean, sd) dnorm(x, mean, sd),
#'   cdf = function(x, mean, sd) pnorm(x, mean, sd),
#'   params = list(mean = 0, sd = 1)
#' )
#' d$cdf_fn(0)   # 0.5
#'
#' # Weibull with only PDF (CDF derived numerically)
#' d2 <- pci_dist(
#'   pdf = function(x, shape, scale) dweibull(x, shape, scale),
#'   params = list(shape = 2, scale = 10),
#'   support = c(0, 60)
#' )
#'
#' @export
pci_dist <- function(pdf, cdf = NULL, params = list(), support = c(-Inf, Inf)) {


  # --- validate ---

  if (!is.function(pdf)) {
    stop("'pdf' must be a function.")
  }
  if (!is.null(cdf) && !is.function(cdf)) {
    stop("'cdf' must be a function or NULL.")
  }
  if (!is.list(params)) {
    stop("'params' must be a named list.")
  }
  if (!is.numeric(support) || length(support) != 2) {
    stop("'support' must be a numeric vector of length 2.")
  }
  if (support[2] <= support[1]) {
    stop("support[2] must be greater than support[1].")
  }

  # --- build closures ---
  pdf_fn <- function(x) {
    do.call(pdf, c(list(x), params))
  }


  if (!is.null(cdf)) {
    cdf_fn <- function(x) {
      do.call(cdf, c(list(x), params))
    }
    cdf_is_numeric <- FALSE
  } else {
    # Derive CDF numerically via integrate
    low <- if (is.finite(support[1])) support[1] else -Inf
    cdf_fn <- function(x) {
      if (length(x) > 1) return(vapply(x, cdf_fn, numeric(1)))
      if (is.finite(support[1]) && x <= support[1]) return(0)
      if (is.finite(support[2]) && x >= support[2]) return(1)
      tryCatch(
        stats::integrate(pdf_fn, lower = low, upper = x,
                         rel.tol = 1e-8, abs.tol = 1e-12)$value,
        error = function(e) {
          warning("Numerical integration failed at x = ", x,
                  ". Returning NA. Error: ", e$message)
          NA_real_
        }
      )
    }
    cdf_is_numeric <- TRUE
  }

  structure(
    list(
      pdf_fn         = pdf_fn,
      cdf_fn         = cdf_fn,
      params         = params,
      support        = support,
      cdf_is_numeric = cdf_is_numeric
    ),
    class = "pci_dist"
  )
}

#' Print method for pci_dist objects
#'
#' @param x A \code{pci_dist} object.
#' @param ... Additional arguments (ignored).
#'
#' @return Invisibly returns the input \code{x} of class \code{"pci_dist"}.
#'   Called for side effect of printing distribution parameters and specification details to the console.
#' @export
print.pci_dist <- function(x, ...) {
  cat("Process Capability Distribution Specification\n")
  cat("  Support : [", x$support[1], ", ", x$support[2], "]\n", sep = "")
  if (length(x$params) > 0) {
    pstr <- paste(names(x$params), unlist(x$params), sep = " = ", collapse = ", ")
    cat("  Parameters: ", pstr, "\n", sep = "")
  }
  if (x$cdf_is_numeric) {
    cat("  CDF: derived numerically via integrate()\n")
  } else {
    cat("  CDF: user-supplied (closed-form)\n")
  }
  invisible(x)
}

# ------------------------------------------------------------------
#' Normal Distribution Shortcut
#'
#' Convenience constructor for a \code{\link{pci_dist}} based on the normal
#' distribution. Useful for sanity-checking that \eqn{C_{py}} collapses to
#' the classical indices when \eqn{F} is the normal CDF.
#'
#' @param mean Mean of the normal distribution.
#' @param sd Standard deviation of the normal distribution.
#' @return An S3 object of class \code{"pci_dist"} encapsulating the normal
#'   distribution PDF and CDF functions, parameter list, and support bounds.
#'
#' @examples
#' d <- pci_dist_normal(mean = 60, sd = 1)
#' d$cdf_fn(60)   # 0.5
#' d$pdf_fn(60)   # ~ 0.3989
#'
#' @export
pci_dist_normal <- function(mean = 0, sd = 1) {
  if (!is.numeric(mean) || length(mean) != 1) stop("'mean' must be a single number.")
  if (!is.numeric(sd) || length(sd) != 1 || sd <= 0) stop("'sd' must be a single positive number.")

  pci_dist(
    pdf    = function(x, mean, sd) stats::dnorm(x, mean = mean, sd = sd),
    cdf    = function(x, mean, sd) stats::pnorm(x, mean = mean, sd = sd),
    params = list(mean = mean, sd = sd),
    support = c(mean - 8 * sd, mean + 8 * sd)
  )
}
