# ---- Input Validation Helpers ----

#' Validate specification limits
#' @param LSL Lower specification limit
#' @param USL Upper specification limit
#' @return Invisibly returns \code{NULL}. Called for side effects (input validation).
#' @keywords internal
validate_spec_limits <- function(LSL, USL) {
  if (!is.null(LSL) && !is.null(USL)) {
    if (!is.numeric(LSL) || !is.numeric(USL)) {
      stop("LSL and USL must be numeric values.")
    }
    if (USL <= LSL) {
      stop("USL must be greater than LSL. Got USL = ", USL, ", LSL = ", LSL, ".")
    }
  }
}

#' Validate that a value is positive
#' @param x Value to check
#' @param name Name of the parameter (for error message)
#' @return Invisibly returns \code{NULL}. Called for side effects (input validation).
#' @keywords internal
validate_positive <- function(x, name) {
  if (!is.null(x)) {
    if (!is.numeric(x) || x <= 0) {
      stop("'", name, "' must be a positive number. Got ", x, ".")
    }
  }
}

#' Validate significance level alpha
#' @param alpha Significance level
#' @return Invisibly returns \code{NULL}. Called for side effects (input validation).
#' @keywords internal
validate_alpha <- function(alpha) {
  if (!is.numeric(alpha) || alpha <= 0 || alpha >= 1) {
    stop("'alpha' must be between 0 and 1 (exclusive). Got ", alpha, ".")
  }
}

#' Validate desired yield p0
#' @param p0 Desired yield
#' @return Invisibly returns \code{NULL}. Called for side effects (input validation).
#' @keywords internal
validate_p0 <- function(p0) {
  if (!is.numeric(p0) || p0 <= 0 || p0 > 1) {
    stop("'p0' must be in (0, 1]. Got ", p0, ".")
  }
}

#' Require a parameter to be non-NULL
#' @param x Value to check
#' @param name Name of the parameter
#' @param index Name of the index requiring this parameter
#' @return Invisibly returns \code{NULL}. Called for side effects (input validation).
#' @keywords internal
require_param <- function(x, name, index) {
  if (is.null(x)) {
    stop("Index '", index, "' requires '", name, "'. Please supply it.")
  }
}

# ---- Inverse-CDF Sampling ----

#' Draw random samples from a pci_dist via inverse-CDF sampling
#'
#' Generates samples by drawing U ~ Uniform(0,1) and inverting the CDF
#' using \code{\link[stats]{uniroot}}.
#'
#' @param dist A \code{pci_dist} object.
#' @param n Number of samples to draw.
#' @return Numeric vector of length \code{n}.
#' @keywords internal
inverse_cdf_sample <- function(dist, n) {
  u <- stats::runif(n)
  support <- dist$support

  # Widen support slightly for numerical safety

  lower <- if (is.finite(support[1])) support[1] else -1e6
  upper <- if (is.finite(support[2])) support[2] else 1e6

  cdf_fn <- dist$cdf_fn

  vapply(u, function(ui) {
    # Handle edge cases
    if (ui <= 0) return(lower)
    if (ui >= 1) return(upper)

    tryCatch(
      stats::uniroot(
        f = function(x) cdf_fn(x) - ui,
        lower = lower,
        upper = upper,
        tol = 1e-8
      )$root,
      error = function(e) {
        # Fallback: try expanding the interval
        tryCatch(
          stats::uniroot(
            f = function(x) cdf_fn(x) - ui,
            lower = lower - abs(lower),
            upper = upper + abs(upper),
            tol = 1e-6
          )$root,
          error = function(e2) NA_real_
        )
      }
    )
  }, numeric(1))
}

# ---- Desirable Limits for Cpy ----

#' Compute symmetric desirable limits for Cpy
#'
#' Given a \code{pci_dist} and desired yield \code{p0}, computes
#' LDL and UDL symmetric about the median of the distribution such
#' that \code{F(UDL) - F(LDL) = p0}.
#'
#' @param dist A \code{pci_dist} object.
#' @param p0 Desired yield (probability), in (0, 1].
#' @return Named numeric vector \code{c(LDL = ..., UDL = ...)}.
#' @keywords internal
compute_desirable_limits <- function(dist, p0) {
  validate_p0(p0)
  cdf_fn <- dist$cdf_fn
  support <- dist$support

  lower <- if (is.finite(support[1])) support[1] else -1e6
  upper <- if (is.finite(support[2])) support[2] else 1e6

  # Find the median
  median_val <- tryCatch(
    stats::uniroot(
      f = function(x) cdf_fn(x) - 0.5,
      lower = lower,
      upper = upper,
      tol = 1e-8
    )$root,
    error = function(e) {
      stop("Could not find the median of the distribution. ",
           "Check the CDF and support. Error: ", e$message)
    }
  )

  # If p0 == 1, use the full support
  if (p0 == 1) {
    return(c(LDL = lower, UDL = upper))
  }

  # Solve for delta such that CDF(median + delta) - CDF(median - delta) = p0
  max_delta <- max(abs(upper - median_val), abs(median_val - lower))

  delta <- tryCatch(
    stats::uniroot(
      f = function(d) cdf_fn(median_val + d) - cdf_fn(median_val - d) - p0,
      lower = 0,
      upper = max_delta,
      tol = 1e-8
    )$root,
    error = function(e) {
      stop("Could not compute desirable limits for p0 = ", p0,
           ". The desired yield may be unachievable for this distribution. ",
           "Error: ", e$message)
    }
  )

  c(LDL = median_val - delta, UDL = median_val + delta)
}

# ---- Helper to compute index from sample statistics ----

#' Compute a PCI from sample statistics
#' @return A numeric value of the calculated process capability index, or \code{NA_real_} on error/invalid input.
#' @keywords internal
compute_index_from_sample <- function(index, sample_data, LSL, USL, target,
                                       LDL, UDL, p0, dist) {
  mu_hat <- mean(sample_data)
  sigma_hat <- stats::sd(sample_data)

  if (index == "Cpy") {
    # Use empirical CDF for Cpy
    ecdf_fn <- stats::ecdf(sample_data)
    p_val <- ecdf_fn(USL) - ecdf_fn(LSL)

    if (!is.null(LDL) && !is.null(UDL)) {
      p0_val <- ecdf_fn(UDL) - ecdf_fn(LDL)
    } else if (!is.null(p0)) {
      p0_val <- p0
    } else {
      p0_val <- NA_real_
    }

    if (is.na(p0_val) || p0_val == 0) return(NA_real_)
    return(p_val / p0_val)
  }

  # For classical/performance indices, use sample stats
  pci(index = index,
      LSL = LSL, USL = USL, target = target,
      mu = mu_hat, sigma = sigma_hat,
      xbar = mu_hat, s = sigma_hat)
}
