#' Log-Likelihood for BARMA Models
#'
#' @title
#' Compute Conditional Log-Likelihood for Beta Autoregressive
#' Moving Average Models
#'
#' @description
#' Computes the conditional log-likelihood of a Beta Autoregressive
#' Moving Average (BARMA) model. This function is designed for users who:
#' \itemize{
#'   \item Implement custom optimization algorithms
#'   \item Verify theoretical properties
#'   \item Conduct simulation studies
#'   \item Debug model fitting
#'   \item Integrate BARMA models into their own workflows
#' }
#'
#' @details
#' The log-likelihood is computed as:
#' \deqn{\ell = \sum_{t=m+1}^{n} \log f(y_t | \mu_t, \phi)}
#'
#' where \eqn{f} is the Beta density with shape parameters
#' \eqn{shape1 = \mu_t * \phi} and \eqn{shape2 = (1-\mu_t)*\phi}.
#'
#' The linear predictor is constructed as:
#' \deqn{\eta_t = \alpha + X_t \beta + \sum_{i=1}^{p} \varphi_i
#' (y_{t-i} - X_{t-i}\beta) + \sum_{j=1}^{q} \theta_j \epsilon_{t-j}}
#'
#' **Important**: This function implements the corrections from the 2017
#' Erratum (Rocha & Cribari-Neto, 2017) for moving average components.
#' See References section for details.
#'
#' **Parameter Order**: Parameters should be supplied in the order:
#' alpha, varphi (AR), theta (MA), phi, beta (regressors).
#' This matches the parameter order used by \code{\link{barma}}.
#'
#' @param y
#' A numeric vector representing the time series data, with values
#' strictly in (0, 1).
#'
#' @param ar
#' A numeric vector specifying the autoregressive (AR) lags
#' (e.g., \code{c(1, 2)}). Defaults to \code{integer(0)}, which omits the
#' AR component entirely. Absence should be expressed by omitting this
#' argument or passing \code{integer(0)}.
#'
#' @param ma
#' A numeric vector specifying the moving average (MA) lags
#' (e.g., \code{1}). Defaults to \code{integer(0)}, which omits the
#' MA component entirely. Absence should be expressed by omitting this
#' argument or passing \code{integer(0)}.
#'
#' @param alpha
#' The intercept term (numeric scalar).
#'
#' @param varphi
#' A numeric vector of autoregressive (AR) parameters.
#' Absence should be expressed by omitting this argument or passing
#' \code{numeric(0)}.
#'
#' @param theta
#' A numeric vector of moving average (MA) parameters.
#' Absence should be expressed by omitting this argument or passing
#' \code{numeric(0)}.
#'
#' @param phi
#' The precision parameter of the BARMA model (must be positive and finite).
#' Larger values indicate less variance for a given mean.
#'
#' @param link
#' A character string specifying the link function:
#' \code{"logit"} (default), \code{"probit"}, or \code{"cloglog"}.
#'
#' @param xreg
#' A matrix or data frame of static regressors (optional).
#' Must have the same number of rows as length of \code{y}.
#'
#' @param beta
#' A numeric vector of regression coefficients for \code{xreg} (optional).
#' Length must match number of columns in \code{xreg}.
#'
#' @param penalty
#' Logical. If \code{TRUE}, a ridge penalty is subtracted from the
#' conditional log-likelihood, yielding the penalized conditional
#' log-likelihood proposed by Cribari-Neto, Costa and Fonseca (2025).
#' The penalty term is \eqn{(n - a) \lambda_n \|\boldsymbol{\nu}\|^2},
#' where \eqn{\lambda_n = 1/(n - a)^{0.9}} and \eqn{\boldsymbol{\nu}}
#' collects \eqn{\alpha}, \eqn{\boldsymbol{\varphi}}, and
#' \eqn{\boldsymbol{\theta}} (the precision parameter \eqn{\phi} and
#' regression coefficients \eqn{\boldsymbol{\beta}} are excluded).
#' Defaults to \code{FALSE}.
#'
#' @return
#' A numeric scalar representing the (penalized) conditional log-likelihood
#' value. When \code{penalty = FALSE} (default), the standard conditional
#' log-likelihood is returned. When \code{penalty = TRUE}, the ridge-penalized
#' conditional log-likelihood of Cribari-Neto, Costa and Fonseca (2025) is
#' returned instead.
#' Returns \code{-Inf} if:
#' \itemize{
#'   \item \code{phi} is non-positive or non-finite
#'   \item Insufficient observations for specified lag structure
#'   \item Fitted values are outside (0, 1)
#'   \item Any numerical issues occur
#' }
#'
#' @references
#' Rocha, A.V., & Cribari-Neto, F. (2009). Beta autoregressive moving
#' average models. \emph{TEST}, 18(3), 529-545.
#' \doi{10.1007/s11749-008-0112-z}
#'
#' Rocha, A.V., & Cribari-Neto, F. (2017). Erratum to: Beta autoregressive
#' moving average models. \emph{TEST}, 26, 451-459.
#' \doi{10.1007/s11749-017-0528-4}
#'
#' Cribari-Neto, F., Costa, E., & Fonseca, R.V. (2025). Numerical stability
#' enhancements in beta autoregressive moving average model estimation.
#' \emph{Brazilian Journal of Probability and Statistics}, 39(4), 410-437.
#' \doi{10.1214/25-BJPS645}
#'
#' @seealso
#' \code{\link{barma}} for model fitting,
#' \code{\link{score_vector_barma}} for gradient computation,
#' \code{\link{fim_barma}} for Fisher Information Matrix
#'
#' @examples
#' \donttest{
#'   # Example 1: Log-likelihood for a BAR(1) model (no MA component)
#'   set.seed(2025)
#'   y_sim_bar1 <- simu_barma(
#'     n      = 250,
#'     alpha  = 0.0,
#'     varphi = 0.6,
#'     phi    = 25.0,
#'     link   = "logit",
#'     freq   = 12
#'   )
#'
#'   loglik_barma(
#'     y      = y_sim_bar1,
#'     ar     = 1,
#'     alpha  = 0.0,
#'     varphi = 0.6,
#'     theta  = numeric(0),
#'     phi    = 25.0,
#'     link   = "logit"
#'   )
#'
#'   # Example 2: Log-likelihood for a BARMA(1, 1) model
#'   set.seed(2025)
#'   y_sim_barma11 <- simu_barma(
#'     n      = 250,
#'     alpha  = 0.0,
#'     varphi = 0.6,
#'     theta  = 0.3,
#'     phi    = 25.0,
#'     link   = "logit",
#'     freq   = 12
#'   )
#'
#'   loglik_barma(
#'     y      = y_sim_barma11,
#'     ar     = 1,
#'     ma     = 1,
#'     alpha  = 0.0,
#'     varphi = 0.6,
#'     theta  = 0.3,
#'     phi    = 25.0,
#'     link   = "logit"
#'   )
#'
#'   # Example 3: Log-likelihood for a BMA(1) model (no AR component)
#'   set.seed(2025)
#'   y_sim_bma1 <- simu_barma(
#'     n      = 250,
#'     alpha  = 0.0,
#'     theta  = 0.3,
#'     phi    = 20.0,
#'     link   = "logit",
#'     freq   = 12
#'   )
#'
#'   loglik_barma(
#'     y      = y_sim_bma1,
#'     ma     = 1,
#'     alpha  = 0.0,
#'     varphi = numeric(0),
#'     theta  = 0.3,
#'     phi    = 20.0,
#'     link   = "logit"
#'   )
#'
#'   # Example 4: Ridge-penalized log-likelihood for a BARMA(1, 1) model
#'   # Uses penalty = TRUE as recommended by Cribari-Neto, Costa and
#'   # Fonseca (2025, BJPS) to improve numerical stability.
#'   loglik_barma(
#'     y       = y_sim_barma11,
#'     ar      = 1,
#'     ma      = 1,
#'     alpha   = 0.0,
#'     varphi  = 0.6,
#'     theta   = 0.3,
#'     phi     = 25.0,
#'     link    = "logit",
#'     penalty = TRUE
#'   )
#' }
#'
#' @export
loglik_barma <- function(
    y,
    ar   = integer(0),
    ma   = integer(0),
    alpha,
    varphi = numeric(0),
    theta = numeric(0),
    phi,
    link = "logit",
    xreg = NULL,
    beta = NULL, 
    penalty = FALSE
) {
  
  # --------------------------------------------------------------------------
  # 1. VALIDATE INPUT PARAMETERS 
  # --------------------------------------------------------------------------
  
  # Validate response variable
  if (!is.numeric(y)) {
    stop("y must be a numeric vector.")
  }
  if (length(y) < 2) {
    stop("y must have at least 2 observations.")
  }
  if (anyNA(y)) {
    stop("y contains missing values (NA).")
  }
  if (min(y) <= 0 || max(y) >= 1) {
    stop(
      "Response values must be strictly in (0, 1). ",
      "Current range: [", round(min(y), 4), ", ", round(max(y), 4), "]."
    )
  }
  
  # Validate precision parameter
  if (!is.numeric(phi) || length(phi) != 1) {
    stop("phi must be a single numeric value.")
  }
  if (phi <= 0 || !is.finite(phi)) {
    return(-Inf)
  }
  
  # --------------------------------------------------------------------------
  # 2. DETERMINE MODEL STRUCTURE 
  # --------------------------------------------------------------------------
  
  # Lag vectors
  ar_lags <- ar
  ma_lags <- ma
  
  # Force varphi / theta to numeric(0) when the corresponding component
  # is absent, so that drop(crossprod(numeric(0), numeric(0))) == 0.
  varphi <- if (length(ar_lags) > 0) varphi else numeric(0)
  theta  <- if (length(ma_lags) > 0) theta  else numeric(0)
  
  # Validate parameter-lag consistency
  if (length(varphi) != length(ar_lags)) {
    stop(
      "Length of 'varphi' (", length(varphi), ") ",
      "must match length of 'ar' (", length(ar_lags), ")."
    )
  }
  if (length(theta) != length(ma_lags)) {
    stop(
      "Length of 'theta' (", length(theta), ") ",
      "must match length of 'ma' (", length(ma_lags), ")."
    )
  }
  
  # Setup Regressors
  if (!is.null(xreg)) {
    if (!is.matrix(xreg)) xreg <- as.matrix(xreg)
    
    if (is.null(beta)) {
      stop("If 'xreg' is provided, 'beta' must also be provided.")
    }
    if (!is.numeric(beta)) {
      stop("beta must be numeric.")
    }
    if (ncol(xreg) != length(beta)) {
      stop(
        "Length of 'beta' (", length(beta), ") ",
        "must match columns of 'xreg' (", ncol(xreg), ")."
      )
    }
    if (nrow(xreg) != length(y)) {
      stop(
        "Number of rows in 'xreg' (", nrow(xreg), ") ",
        "must match length of 'y' (", length(y), ")."
      )
    }
    if (anyNA(xreg)) {
      stop("xreg contains missing values (NA).")
    }
    
    # Pre-compute X * beta for efficiency (vectorized operation)
    xb <- as.vector(xreg %*% beta)
  } else {
    # Zero contribution from regressors if none provided
    xb <- numeric(length(y))
  }
  
  # --------------------------------------------------------------------------
  # 3. SETUP LINK FUNCTIONS AND TIME SERIES PROPERTIES
  # --------------------------------------------------------------------------
  
  # Get link function structures
  link_structure <- make_link_structure(link)
  linkfun <- link_structure$linkfun
  linkinv <- link_structure$linkinv
  
  # Transform response using link function
  y_transformed <- linkfun(y)
  
  # Determine maximum lag
  ar_order <- if (length(ar_lags) > 0) max(ar_lags) else 0
  ma_order <- if (length(ma_lags) > 0) max(ma_lags) else 0
  max_lag  <- max(ar_order, ma_order)
  n_obs <- length(y)
  
  # Check for sufficient observations
  if (n_obs <= max_lag) {
    warning(
      "Insufficient observations: have ", n_obs, 
      " observations but need > ", max_lag, " (max lag order)."
    )
    return(-Inf)
  }
  
  # --------------------------------------------------------------------------
  # 4. CALCULATE ERROR AND PREDICTOR
  # --------------------------------------------------------------------------
  
  # Initialize containers for linear predictor and errors
  # Observations 1:max_lag will have 0/NA values
  error <- rep(0, n_obs)
  eta   <- rep(NA_real_, n_obs)
  
  # Recursively compute predictor and errors.
  for (t in (max_lag + 1):n_obs) {
    eta[t] <- alpha + xb[t] +
      drop(crossprod(varphi, y_transformed[t - ar_lags] - xb[t - ar_lags])) +
      drop(crossprod(theta,  error[t - ma_lags]))
    
    error[t] <- y_transformed[t] - eta[t]
  }
  
  # --------------------------------------------------------------------------
  # 5. CALCULATE THE FINAL LOG-LIKELIHOOD
  # --------------------------------------------------------------------------
  
  # Extract observations used in likelihood
  idx_effective <- (max_lag + 1):n_obs
  eta_eff <- eta[idx_effective]
  y_eff   <- y[idx_effective]
  
  # Transform linear predictor to mean scale
  mu_eff <- linkinv(eta = eta_eff)
  
  # Check for valid mu values (must be in (0,1))
  if (any(mu_eff <= 0 | mu_eff >= 1 | !is.finite(mu_eff))) {
    return(-Inf)
  }
  
  # Compute log-likelihood using Beta density
  # dbeta parameterization: shape1 = mu*phi, shape2 = (1-mu)*phi
  ll_terms <- dbeta(
    y_eff,
    shape1 = mu_eff * phi,
    shape2 = (1 - mu_eff) * phi,
    log = TRUE
  )
  
  # Check for numerical issues (non-finite values)
  if (any(!is.finite(ll_terms))) {
    return(-Inf)
  }
  
  # Accumulate log-likelihood before optional penalization
  sum_ll_terms <- sum(ll_terms)
  
  # --------------------------------------------------------------------------
  # 6. RIDGE PENALIZATION
  # --------------------------------------------------------------------------
  if (penalty) {
    
    # Penalized log-likelihood per Equation (2) of Cribari-Neto, Costa and
    # Fonseca (2025, BJPS, Section 2):
    #   ell_pen(nu) = ell(nu) - (n - a) * lambda_n * ||nu||^2
    # where lambda_n is computed by .barma_ridge_lambda().
    # phi and beta are excluded from penalization (see paper, Section 2).
    lambda <- .barma_ridge_lambda(n_obs, max_lag)
    
    # L2 norm of penalized parameters (beta excluded per the paper)
    l2_norm_sq   <- alpha^2 + sum(varphi^2) + sum(theta^2)
    penalty_term <- (n_obs - max_lag) * lambda * l2_norm_sq
    
    sum_ll_terms <- sum_ll_terms - penalty_term
    
  }
  
  return(sum_ll_terms)
  
}