# ============================================================
# KRONX :: kronx_build.R
# Construction of the KRONX pipeline:
#   Q = diag(1 - epsilon) %*% A
#   K = Q - I
#   N = -K^{-1}
# plus quasi-stationary distribution, residence-weight vector,
# and residence-weighted ruin bound.
# ============================================================

# Expected spell length in each state: E[tau_i] = 1 / (1 - a_{ii}).
expected_spell_lengths <- function(A) {
  1 / pmax(1 - diag(A), 1e-12)
}

# Talebian hazard: epsilon_i = epsilon_min + c_hazard / nu_i.
# Fatter tails (smaller nu_i) → larger hazard.
hazard_from_nu <- function(nu, epsilon_min = 0.01, c_hazard = 0.05) {
  epsilon_min + c_hazard / nu
}

# Hazard-adjusted transition operator Q = diag(1 - epsilon_i) %*% A.
build_Q <- function(A, epsilon) {
  diag(1 - epsilon, nrow = length(epsilon)) %*% A
}

# KRONX generator K = Q - I.
build_K <- function(Q) {
  Q - diag(nrow(Q))
}

# Fundamental matrix N = -K^{-1}.
fundamental_matrix <- function(K) {
  solve(-K)
}

# Quasi-stationary distribution: dominant left eigenvector of Q, normalised.
quasi_stationary_distribution <- function(Q) {
  ev  <- eigen(t(Q))
  idx <- which.max(Re(ev$values))
  vec <- abs(Re(ev$vectors[, idx]))
  safe_normalize(vec)
}

# Residence-weight vector derived from the fundamental matrix N.
# w = init %*% N, then normalised.
residence_weight_vector <- function(N, init = NULL) {
  K <- nrow(N)
  if (is.null(init)) init <- rep(1 / K, K)
  safe_normalize(as.numeric(init %*% N))
}

# State-wise left-tail probability q_i at a given loss threshold.
# Uses pnorm for Gaussian states and pt for Student-t states.
left_tail_state_probs <- function(fit, threshold) {
  if (fit$model == "gaussian") {
    stats::pnorm(threshold,
                 mean = fit$params$mu,
                 sd   = fit$params$sigma)
  } else {
    sapply(seq_along(fit$params$mu), function(k) {
      stats::pt(
        (threshold - fit$params$mu[k]) / fit$params$sigma[k],
        df = fit$params$nu[k]
      )
    })
  }
}
