#' Bootstrap Confidence Interval for a Process Capability Index
#'
#' Computes a confidence interval for a PCI via parametric or nonparametric
#' bootstrap.
#'
#' @details
#' **Parametric bootstrap** (when \code{data = NULL}): \code{B} samples of size
#' \code{n} are drawn from \code{dist} via inverse-CDF sampling.
#' For each sample the process mean, standard deviation (and, for
#' \eqn{C_{py}}, the empirical CDF) are estimated, and the index is
#' recomputed.
#'
#' **Nonparametric bootstrap** (when \code{data} is supplied): \code{B}
#' resamples of the same length are drawn with replacement from \code{data}.
#'
#' Two interval methods are available:
#' \describe{
#'   \item{percentile}{Uses the \code{alpha/2} and \code{1 - alpha/2}
#'     quantiles of the bootstrap distribution.}
#'   \item{bca}{Bias-corrected and accelerated interval using jackknife
#'     acceleration and bootstrap bias correction (Efron, 1987).}
#' }
#'
#' @param index Character: one of the 12 index names (see \code{\link{pci}}).
#' @param dist A \code{\link{pci_dist}} object.
#' @param n Integer sample size.
#' @param data Optional numeric vector of observed data. If supplied,
#'   nonparametric bootstrap is used instead of parametric.
#' @param LSL,USL Specification limits.
#' @param target Target value (for \code{Cpm}/\code{Cpmk}).
#' @param LDL,UDL Desirable limits (for \code{Cpy}).
#' @param p0 Desired yield (for \code{Cpy}).
#' @param alpha Significance level; the interval covers \eqn{1 - \alpha}.
#'   Default 0.05.
#' @param B Number of bootstrap replications. Default 2000.
#' @param method Character: \code{"percentile"} (default) or \code{"bca"}.
#'
#' @return An S3 object of class \code{"pci_ci"} with components:
#'   \item{index}{The index name.}
#'   \item{estimate}{Point estimate.}
#'   \item{lower}{Lower CI bound.}
#'   \item{upper}{Upper CI bound.}
#'   \item{alpha}{The significance level used.}
#'   \item{method}{The interval method used.}
#'   \item{boot_values}{Numeric vector of all \code{B} bootstrap estimates.}
#'
#' @examples
#' d <- pci_dist_normal(mean = 60, sd = 1)
#' ci <- pci_ci("Cp", dist = d, n = 30, LSL = 57, USL = 63,
#'              alpha = 0.05, B = 500)
#' print(ci)
#'
#' @seealso \code{\link{pci}}, \code{\link{pci_grid}}
#' @export
pci_ci <- function(index, dist, n,
                   data    = NULL,
                   LSL     = NULL, USL    = NULL, target = NULL,
                   LDL     = NULL, UDL    = NULL, p0     = NULL,
                   alpha   = 0.05,
                   B       = 2000,
                   method  = c("percentile", "bca")) {

  method <- match.arg(method)
  validate_alpha(alpha)

  valid_indices <- c("Cp", "Cpk", "Cpu", "Cpl", "Cpm", "Cpmk",
                     "Pp", "Ppk", "Ppu", "Ppl", "Z", "Cpy")
  index <- match.arg(index, valid_indices)

  if (!is.numeric(B) || B < 1) stop("'B' must be a positive integer.")
  B <- as.integer(B)

  # ---- helper: compute index from a sample vector -----------------------
  idx_from_sample <- function(samp) {
    mu_hat    <- mean(samp)
    sigma_hat <- stats::sd(samp)

    if (index == "Cpy") {
      ecdf_fn <- stats::ecdf(samp)
      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 {
        return(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
    tryCatch(
      pci(index  = index,
          LSL    = LSL,  USL    = USL,  target = target,
          mu     = mu_hat, sigma = sigma_hat,
          xbar   = mu_hat, s     = sigma_hat),
      error = function(e) NA_real_
    )
  }

  # ---- point estimate ---------------------------------------------------
  if (!is.null(data)) {
    data <- as.numeric(data)
    n    <- length(data)
    estimate <- idx_from_sample(data)
  } else {
    # parametric: draw one "reference" sample for the estimate
    if (missing(dist) || is.null(dist)) {
      stop("Either 'data' or 'dist' must be supplied.")
    }
    if (missing(n) || is.null(n)) {
      stop("'n' (sample size) must be supplied for parametric bootstrap.")
    }
    ref_sample <- inverse_cdf_sample(dist, n)
    estimate   <- idx_from_sample(ref_sample)
  }

  # ---- bootstrap --------------------------------------------------------
  boot_values <- numeric(B)

  if (!is.null(data)) {
    # nonparametric bootstrap
    for (b in seq_len(B)) {
      samp <- sample(data, size = n, replace = TRUE)
      boot_values[b] <- idx_from_sample(samp)
    }
  } else {
    # parametric bootstrap
    for (b in seq_len(B)) {
      samp <- inverse_cdf_sample(dist, n)
      boot_values[b] <- idx_from_sample(samp)
    }
  }

  # Remove NAs
  boot_ok <- boot_values[!is.na(boot_values)]
  if (length(boot_ok) < 10) {
    warning("Fewer than 10 valid bootstrap replicates. CI may be unreliable.")
  }

  # ---- confidence limits ------------------------------------------------
  if (method == "percentile") {
    lower <- as.numeric(stats::quantile(boot_ok, probs = alpha / 2))
    upper <- as.numeric(stats::quantile(boot_ok, probs = 1 - alpha / 2))

  } else {
    # BCa method
    # bias correction factor
    z0 <- stats::qnorm(mean(boot_ok < estimate))

    # acceleration via jackknife
    if (!is.null(data)) {
      jack_data <- data
    } else {
      jack_data <- ref_sample
    }

    nn <- length(jack_data)
    jack_vals <- numeric(nn)
    for (i in seq_len(nn)) {
      jack_vals[i] <- idx_from_sample(jack_data[-i])
    }
    theta_dot <- mean(jack_vals, na.rm = TRUE)
    diffs     <- theta_dot - jack_vals
    a_num     <- sum(diffs^3, na.rm = TRUE)
    a_den     <- 6 * (sum(diffs^2, na.rm = TRUE))^1.5
    acc       <- if (a_den == 0) 0 else a_num / a_den

    # adjusted quantiles
    z_lo <- stats::qnorm(alpha / 2)
    z_hi <- stats::qnorm(1 - alpha / 2)

    alpha1 <- stats::pnorm(z0 + (z0 + z_lo) / (1 - acc * (z0 + z_lo)))
    alpha2 <- stats::pnorm(z0 + (z0 + z_hi) / (1 - acc * (z0 + z_hi)))

    # Clamp to [0, 1]
    alpha1 <- max(min(alpha1, 1), 0)
    alpha2 <- max(min(alpha2, 1), 0)

    lower <- as.numeric(stats::quantile(boot_ok, probs = alpha1))
    upper <- as.numeric(stats::quantile(boot_ok, probs = alpha2))
  }

  structure(
    list(
      index       = index,
      estimate    = estimate,
      lower       = lower,
      upper       = upper,
      alpha       = alpha,
      method      = method,
      boot_values = boot_values
    ),
    class = "pci_ci"
  )
}


#' Print method for pci_ci objects
#'
#' @param x A \code{pci_ci} object.
#' @param ... Additional arguments (ignored).
#'
#' @return Invisibly returns the input \code{x} of class \code{"pci_ci"}.
#'   Called for side effect of printing formatted confidence interval details to the console.
#' @export
print.pci_ci <- function(x, ...) {
  pct <- round((1 - x$alpha) * 100, 1)
  cat(sprintf("  Index    : %s\n", x$index))
  cat(sprintf("  Estimate : %.4f\n", x$estimate))
  cat(sprintf("  %g%% CI [%s]: (%.4f, %.4f)\n",
              pct, x$method, x$lower, x$upper))
  invisible(x)
}
