#' Uncensored Data Parameter Estimation via Maximum Likelihood
#'
#' Fits process distribution parameters to observed uncensored numeric data using
#' Maximum Likelihood Estimation (MLE).
#'
#' @param data Numeric vector of observed process observations.
#' @param distribution A \code{gpci_dist} object.
#' @param method Optimization method (default \code{"mle"}).
#' @param initial Named numeric vector or list of initial parameter values. If \code{NULL},
#'   sensible defaults or sample moments are used.
#' @return An updated \code{gpci_dist} object with estimated parameters.
#' @export
#' @examples
#' data_sample <- rnorm(100, mean = 10, sd = 2)
#' dist_fitted <- fit_distribution(data_sample, dist_normal())
#' dist_fitted$params
fit_distribution <- function(data, distribution, method = "mle", initial = NULL) {
  data <- data[is.finite(data)]
  if (length(data) < 3) {
    stop("Insufficient valid numeric data for parameter estimation.")
  }
  
  # Auto-detect parameter names from pdf formals if empty
  if (length(distribution$params) == 0 && !is.null(distribution$pdf)) {
    f_args <- formals(distribution$pdf)
    f_args <- f_args[!names(f_args) %in% c("x", "p", "...")]
    param_list <- list()
    for (nm in names(f_args)) {
      val <- tryCatch(eval(f_args[[nm]]), error = function(e) 1)
      if (!is.numeric(val) || length(val) != 1 || is.na(val)) val <- 1
      param_list[[nm]] <- val
    }
    if (length(param_list) > 0) {
      distribution$params <- param_list
    }
  }
  
  params <- distribution$params
  param_names <- names(params)
  
  if (length(param_names) == 0) {
    return(distribution)
  }
  
  # Initial estimates fallback if not supplied
  if (is.null(initial)) {
    initial <- params
    if (distribution$name == "Normal") {
      initial$mean <- mean(data)
      initial$sd <- max(1e-4, stats::sd(data))
    } else if (distribution$name == "Exponential") {
      initial$rate <- max(1e-4, 1 / mean(data))
    } else if (distribution$name == "Gamma") {
      m <- mean(data)
      v <- max(1e-4, stats::var(data))
      initial$shape <- max(1e-2, m^2 / v)
      initial$rate <- max(1e-2, m / v)
    } else if (distribution$name == "Weibull") {
      initial$shape <- 1
      initial$scale <- max(1e-4, mean(data))
    } else if (distribution$name == "Logistic-Exponential") {
      initial$alpha <- 1
      initial$lambda <- max(1e-4, 1 / mean(data))
    }
  }
  
  init_vec <- unlist(initial[param_names])
  
  neg_log_lik <- function(par_vec) {
    names(par_vec) <- param_names
    # Positivity bounds check for scale/shape parameters
    for (nm in param_names) {
      if (nm %in% c("sd", "rate", "shape", "scale", "alpha", "lambda") && par_vec[[nm]] <= 1e-8) {
        return(1e10)
      }
    }
    
    par_list <- as.list(par_vec)
    densities <- tryCatch({
      do.call(distribution$pdf, c(list(data), par_list))
    }, error = function(e) rep(0, length(data)))
    
    densities <- pmax(1e-12, densities)
    if (any(!is.finite(densities)) || any(densities <= 0)) return(1e10)
    -sum(log(densities))
  }
  
  opt_res <- tryCatch({
    stats::optim(init_vec, neg_log_lik, method = "Nelder-Mead", control = list(maxit = 2000))
  }, error = function(e) NULL)
  
  fitted_params <- params
  if (!is.null(opt_res) && opt_res$convergence == 0) {
    fitted_vec <- opt_res$par
    names(fitted_vec) <- param_names
    fitted_params <- as.list(fitted_vec)
  } else {
    fitted_params <- initial
  }
  
  distribution$params <- fitted_params
  distribution
}
