*! _2snardl_mata.mata
*! Mata routines for twostep_nardl
*! Based on Cho, Greenwood-Nimmo & Shin (2019)
*! Two-Step Estimation of the NARDL Model

version 14

// Drop existing definitions to allow re-sourcing
capture mata: mata drop _2snardl_psum()
capture mata: mata drop _2snardl_neweywest()
capture mata: mata drop _2snardl_fmols()
capture mata: mata drop _2snardl_fmtols()
capture mata: mata drop _2snardl_wald_lr_k1()
capture mata: mata drop _2snardl_wald_general()
capture mata: mata drop _2snardl_wald_sr()
capture mata: mata drop _2snardl_dynmult()
capture mata: mata drop _2snardl_optimlag()
capture mata: mata drop _2snardl_run_step1_k1()
capture mata: mata drop _2snardl_run_step1_kn()
capture mata: mata drop _2snardl_run_wald_lr_k1()
capture mata: mata drop _2snardl_run_wald_lr_kn()
capture mata: mata drop _2snardl_run_wald_sr()
capture mata: mata drop _2snardl_run_dynmult()
capture mata: mata drop _2snardl_hacresult()
capture mata: mata drop _2snardl_fmols_result()
capture mata: mata drop _2snardl_fmtols_result()
capture mata: mata drop _2snardl_wald_result()

// ============================================================================
// PARTIAL SUM DECOMPOSITION
// ============================================================================
mata:
mata set matastrict on

void _2snardl_psum(string scalar xvar, string scalar touse, ///
                   string scalar posvar, string scalar negvar, ///
                   real scalar threshold)
{
    real colvector x, dx, xpos, xneg
    real scalar T, i

    x = st_data(., xvar, touse)
    T = rows(x)

    dx   = J(T, 1, 0)
    xpos = J(T, 1, 0)
    xneg = J(T, 1, 0)

    dx[1] = 0
    for (i = 2; i <= T; i++) {
        dx[i] = x[i] - x[i-1]
    }

    xpos[1] = max((dx[1] - threshold, 0))
    xneg[1] = min((dx[1] - threshold, 0))
    for (i = 2; i <= T; i++) {
        xpos[i] = xpos[i-1] + max((dx[i] - threshold, 0))
        xneg[i] = xneg[i-1] + min((dx[i] - threshold, 0))
    }

    st_store(., posvar, touse, xpos)
    st_store(., negvar, touse, xneg)
}

end


// ============================================================================
// NEWEY-WEST HAC COVARIANCE ESTIMATOR
// ============================================================================
mata:
mata set matastrict on

struct _2snardl_hacresult {
    real matrix Sigma
    real matrix Pi
}

struct _2snardl_hacresult scalar _2snardl_neweywest(real matrix G, ///
                                                    real scalar bw)
{
    real scalar T, m, ell, kk
    real scalar omega_k
    real matrix Gamma0, Gamma_k, Sigma, Pi
    struct _2snardl_hacresult scalar result

    T = rows(G)
    m = cols(G)

    if (bw <= 0) {
        ell = floor(T^0.25)
    }
    else {
        ell = bw
    }

    G = G :- mean(G)

    Gamma0 = cross(G, G) / T
    Sigma = Gamma0
    Pi = Gamma0

    for (kk = 1; kk <= ell; kk++) {
        Gamma_k = cross(G[1::T-kk, .], G[kk+1::T, .]) / T
        omega_k = 1 - kk / (1 + ell)

        Sigma = Sigma + omega_k * (Gamma_k + Gamma_k')
        Pi = Pi + Gamma_k'
    }

    result.Sigma = Sigma
    result.Pi    = Pi

    return(result)
}

end


// ============================================================================
// FM-OLS ESTIMATOR (k=1 decomposed variable, + optional linear variables)
// ============================================================================
mata:
mata set matastrict on

struct _2snardl_fmols_result {
    real colvector beta_lr
    real colvector beta_lin
    real matrix    V_lr
    real matrix    V_lin
    real matrix    V_lr_full
    real scalar    alpha
    real scalar    lambda
    real scalar    eta
    real colvector rho_vec
    real matrix    V_rho
    real scalar    tau2
    real colvector uhat
    real colvector ect
    real scalar    method
    real scalar    n_lin
}

struct _2snardl_fmols_result scalar _2snardl_fmols(real colvector y, ///
                                                     real colvector xpos, ///
                                                     real colvector x, ///
                                                     real colvector xneg, ///
                                                     real matrix    zlin, ///
                                                     real scalar    use_fmols, ///
                                                     real scalar    bw)
{
    struct _2snardl_fmols_result scalar result
    struct _2snardl_hacresult scalar hac

    real scalar T, i, n_lin, n_nonstat
    real matrix Q, QQ, QQinv
    real colvector rho_hat, uhat
    real matrix G
    real colvector dx
    real matrix dz
    real matrix Sigma_hat, Pi_hat
    real matrix Sigma11, sigma12_vec, sigma21_vec
    real scalar sigma22_sc
    real colvector y_tilde
    real colvector nu_hat
    real matrix S
    real colvector Qy_adj
    real matrix J_delta

    T = rows(y)
    n_lin = cols(zlin)
    result.n_lin = n_lin

    // Build Q matrix: [1, x_pos, x, z1, ..., zm]
    Q = J(T, 1, 1), xpos, x
    if (n_lin > 0) {
        Q = Q, zlin
    }

    // OLS estimate
    QQ    = cross(Q, Q)
    QQinv = invsym(QQ)
    rho_hat = QQinv * cross(Q, y)

    // Residuals
    uhat = y - Q * rho_hat

    // First differences of x (the original asymmetric variable)
    dx    = J(T, 1, 0)
    dx[1] = 0
    for (i = 2; i <= T; i++) {
        dx[i] = x[i] - x[i-1]
    }

    // First differences of linear variables
    if (n_lin > 0) {
        dz = J(T, n_lin, 0)
        for (i = 2; i <= T; i++) {
            dz[i, .] = zlin[i, .] - zlin[i-1, .]
        }
    }

    if (use_fmols == 0) {
        // ---- OLS only ----
        result.alpha   = rho_hat[1]
        result.lambda  = rho_hat[2]
        result.eta     = rho_hat[3]
        result.rho_vec = rho_hat
        result.tau2    = variance(uhat)
        result.V_rho   = result.tau2 * QQinv
        result.method  = 1
    }
    else {
        // ---- FM-OLS ----
        // G matrix: [dx, dz1, ..., dzm, uhat]
        // Non-stationary directions: x, z1, ..., zm
        n_nonstat = 1 + n_lin
        G = dx
        if (n_lin > 0) {
            G = G, dz
        }
        G = G, uhat

        hac = _2snardl_neweywest(G, bw)
        Sigma_hat = hac.Sigma
        Pi_hat    = hac.Pi

        // Partition: first n_nonstat cols are non-stationary, last col is u
        Sigma11 = Sigma_hat[1::n_nonstat, 1::n_nonstat]
        sigma12_vec = Sigma_hat[1::n_nonstat, n_nonstat+1]
        sigma21_vec = Sigma_hat[n_nonstat+1, 1::n_nonstat]'
        sigma22_sc  = Sigma_hat[n_nonstat+1, n_nonstat+1]

        // FM-adjusted dependent variable
        // G_nonstat = [dx, dz] ; projection removes endogeneity
        y_tilde = y - (G[., 1::n_nonstat]) * invsym(Sigma11) * sigma12_vec

        // Bias correction vector
        nu_hat = Pi_hat[1::n_nonstat, n_nonstat+1] - Pi_hat[1::n_nonstat, 1::n_nonstat] * invsym(Sigma11) * sigma12_vec

        // Selection matrix: maps Q [1, xpos, x, z1, ..., zm] to the non-stationary part
        // Non-stationary regressors are x (col 3), z1 (col 4), ..., zm (col 3+n_lin)
        // S is n_nonstat x (3+n_lin) mapping
        S = J(n_nonstat, 2, 0), I(n_nonstat)

        // FM-OLS estimator
        Qy_adj  = cross(Q, y_tilde) - T * S' * nu_hat
        rho_hat = QQinv * Qy_adj

        // Long-run variance tau^2
        result.tau2 = sigma22_sc - sigma21_vec' * invsym(Sigma11) * sigma12_vec

        result.alpha   = rho_hat[1]
        result.lambda  = rho_hat[2]
        result.eta     = rho_hat[3]
        result.rho_vec = rho_hat
        result.V_rho   = result.tau2 * QQinv
        result.method  = 2

        // Recompute residuals with FM-OLS estimates
        uhat = y - Q * rho_hat
    }

    // Recover beta_pos = lambda + eta, beta_neg = eta
    result.beta_lr = (rho_hat[2] + rho_hat[3] \ rho_hat[3])

    // VCE for (beta_pos, beta_neg) via delta method
    // rho = [alpha, lambda, eta, gamma1, ..., gamma_m]
    // beta_pos = lambda + eta => d/d_alpha=0, d/d_lambda=1, d/d_eta=1, d/d_gamma_i=0
    // beta_neg = eta          => d/d_alpha=0, d/d_lambda=0, d/d_eta=1, d/d_gamma_i=0
    J_delta = (0, 1, 1, J(1, n_lin, 0) \ 0, 0, 1, J(1, n_lin, 0))
    result.V_lr = J_delta * result.V_rho * J_delta'

    // Linear variable LR coefficients: gamma_1, ..., gamma_m
    if (n_lin > 0) {
        result.beta_lin = rho_hat[4::3+n_lin]
        // VCE for linear variable LR coefficients
        // J_lin maps rho to gamma: zeros for [alpha, lambda, eta], I for [gamma]
        result.V_lin = result.V_rho[4::3+n_lin, 4::3+n_lin]
    }
    else {
        result.beta_lin = J(0, 1, 0)
        result.V_lin = J(0, 0, 0)
    }

    // Full LR VCE: [beta_pos, beta_neg, gamma1, ..., gamma_m]
    if (n_lin > 0) {
        // J_full: maps rho_vec to [beta_pos, beta_neg, gamma1, ..., gamma_m]
        result.V_lr_full = (J_delta \ (J(n_lin, 3, 0), I(n_lin))) * result.V_rho * (J_delta \ (J(n_lin, 3, 0), I(n_lin)))'
    }
    else {
        result.V_lr_full = result.V_lr
    }

    result.uhat = uhat

    // ECT = y - alpha - beta_pos * x_pos - beta_neg * x_neg - gamma' * z
    result.ect = y :- rho_hat[1] :- result.beta_lr[1] * xpos :- result.beta_lr[2] * xneg
    if (n_lin > 0) {
        result.ect = result.ect :- zlin * result.beta_lin
    }

    return(result)
}

end


// ============================================================================
// FM-TOLS ESTIMATOR (k>1 decomposed variables, + optional linear variables)
// ============================================================================
mata:
mata set matastrict on

struct _2snardl_fmtols_result {
    real matrix    beta_pos
    real matrix    beta_neg
    real colvector beta_lin
    real matrix    V_lr
    real matrix    V_lin
    real matrix    V_lr_full
    real colvector rho_vec
    real matrix    V_rho
    real scalar    tau2
    real colvector uhat
    real colvector ect
    real scalar    method
    real scalar    n_lin
}

struct _2snardl_fmtols_result scalar _2snardl_fmtols( ///
    real colvector y, ///
    real matrix xpos, ///
    real matrix x, ///
    real matrix xneg, ///
    real matrix zlin, ///
    real scalar use_fm, ///
    real scalar bw)
{
    struct _2snardl_fmtols_result scalar result
    struct _2snardl_hacresult scalar hac

    real scalar T, kk, n_lin, i, n_nonstat
    real colvector tt
    real matrix mhat, dmhat, dx, dz
    real matrix R, RR, RRinv
    real colvector rho_hat, uhat
    real matrix G
    real matrix Sigma_bar, Pi_bar
    real matrix Sigma11
    real colvector sigma12_vec, sigma21_vec
    real scalar sigma22_sc
    real colvector y_bar, nu_bar
    real matrix Sbar, ell_t
    real colvector Ry_adj
    real colvector xp_i
    real scalar mu_hat_i, denom
    real colvector lambda_hat, eta_hat
    real matrix J_delta

    T = rows(y)
    kk = cols(xpos)
    n_lin = cols(zlin)
    result.n_lin = n_lin

    // --- Step 1a: Detrend x_pos ---
    tt = (1::T)
    mhat = J(T, kk, 0)

    for (i = 1; i <= kk; i++) {
        xp_i = xpos[., i]
        denom = cross(tt, tt)
        mu_hat_i = cross(tt, xp_i) / denom
        mhat[., i] = xp_i :- mu_hat_i * tt
    }

    // First differences
    dmhat = J(T, kk, 0)
    dx    = J(T, kk, 0)
    for (i = 2; i <= T; i++) {
        dmhat[i, .] = mhat[i, .] - mhat[i-1, .]
        dx[i, .]    = x[i, .] - x[i-1, .]
    }

    // First differences of linear variables
    if (n_lin > 0) {
        dz = J(T, n_lin, 0)
        for (i = 2; i <= T; i++) {
            dz[i, .] = zlin[i, .] - zlin[i-1, .]
        }
    }

    // --- Step 1b: Build regressor matrix R = [1, t, m_hat, x, z] ---
    R = J(T, 1, 1), tt, mhat, x
    if (n_lin > 0) {
        R = R, zlin
    }

    // OLS
    RR    = cross(R, R)
    RRinv = invsym(RR)
    rho_hat = RRinv * cross(R, y)
    uhat = y - R * rho_hat

    // Non-stationary directions: dmhat (kk cols), dx (kk cols), dz (n_lin cols)
    n_nonstat = 2*kk + n_lin

    if (use_fm == 0) {
        result.tau2   = variance(uhat)
        result.V_rho  = result.tau2 * RRinv
        result.method = 3
    }
    else {
        G = dmhat, dx
        if (n_lin > 0) {
            G = G, dz
        }
        G = G, uhat

        hac = _2snardl_neweywest(G, bw)
        Sigma_bar = hac.Sigma
        Pi_bar    = hac.Pi

        Sigma11     = Sigma_bar[1::n_nonstat, 1::n_nonstat]
        sigma12_vec = Sigma_bar[1::n_nonstat, n_nonstat+1]
        sigma21_vec = Sigma_bar[n_nonstat+1, 1::n_nonstat]'
        sigma22_sc  = Sigma_bar[n_nonstat+1, n_nonstat+1]

        ell_t = dmhat, dx
        if (n_lin > 0) {
            ell_t = ell_t, dz
        }

        y_bar = y - ell_t * invsym(Sigma11) * sigma12_vec

        nu_bar = Pi_bar[1::n_nonstat, n_nonstat+1] - Pi_bar[1::n_nonstat, 1::n_nonstat] * invsym(Sigma11) * sigma12_vec

        // Sbar maps R cols to non-stationary directions
        // R = [1, t, m_hat(kk), x(kk), z(n_lin)]
        // Non-stat regressors start at col 3: m_hat(kk), x(kk), z(n_lin) = 2*kk+n_lin cols
        Sbar = J(n_nonstat, 2, 0), I(n_nonstat)

        Ry_adj  = cross(R, y_bar) - T * Sbar' * nu_bar
        rho_hat = RRinv * Ry_adj
        uhat    = y - R * rho_hat

        result.tau2   = sigma22_sc - sigma21_vec' * invsym(Sigma11) * sigma12_vec
        result.V_rho  = result.tau2 * RRinv
        result.method = 4
    }

    result.rho_vec = rho_hat

    // rho_hat = [alpha, trend, lambda_1..lambda_kk, eta_1..eta_kk, gamma_1..gamma_n_lin]
    // positions: 1=alpha, 2=trend, 3..2+kk=lambda, 3+kk..2+2*kk=eta, 3+2*kk..2+2*kk+n_lin=gamma
    lambda_hat = rho_hat[3::2+kk]
    eta_hat    = rho_hat[3+kk::2+2*kk]

    result.beta_pos = lambda_hat + eta_hat
    result.beta_neg = eta_hat

    // Linear variable LR coefficients
    if (n_lin > 0) {
        result.beta_lin = rho_hat[3+2*kk::2+2*kk+n_lin]
    }
    else {
        result.beta_lin = J(0, 1, 0)
    }

    // Delta method for VCE of [beta_pos, beta_neg]
    // J_delta maps rho to [beta_pos, beta_neg]:
    //   beta_pos_i = lambda_i + eta_i
    //   beta_neg_i = eta_i
    // rho has 2+2*kk+n_lin elements
    J_delta = J(kk, 1, 0), J(kk, 1, 0), I(kk), I(kk), J(kk, n_lin, 0) \ ///
              J(kk, 1, 0), J(kk, 1, 0), J(kk, kk, 0), I(kk), J(kk, n_lin, 0)
    result.V_lr = J_delta * result.V_rho * J_delta'

    // VCE for linear variables
    if (n_lin > 0) {
        result.V_lin = result.V_rho[3+2*kk::2+2*kk+n_lin, 3+2*kk::2+2*kk+n_lin]
    }
    else {
        result.V_lin = J(0, 0, 0)
    }

    // Full LR VCE: [beta_pos, beta_neg, gamma]
    if (n_lin > 0) {
        result.V_lr_full = (J_delta \ (J(n_lin, 2, 0), J(n_lin, 2*kk, 0), I(n_lin))) * result.V_rho * (J_delta \ (J(n_lin, 2, 0), J(n_lin, 2*kk, 0), I(n_lin)))'
    }
    else {
        result.V_lr_full = result.V_lr
    }

    result.uhat = uhat

    // ECT = y - alpha - beta_pos' * x_pos - beta_neg' * x_neg - gamma' * z
    result.ect = y :- rho_hat[1] :- xpos * result.beta_pos :- xneg * result.beta_neg
    if (n_lin > 0) {
        result.ect = result.ect :- zlin * result.beta_lin
    }

    return(result)
}

end


// ============================================================================
// WALD TEST FOR SYMMETRY
// ============================================================================
mata:
mata set matastrict on

struct _2snardl_wald_result {
    real scalar W
    real scalar df
    real scalar p
}

struct _2snardl_wald_result scalar _2snardl_wald_lr_k1( ///
    real scalar lambda_hat, ///
    real scalar V_lambda, ///
    real scalar r)
{
    struct _2snardl_wald_result scalar result

    result.W  = (lambda_hat - r)^2 / V_lambda
    result.df = 1
    result.p  = 1 - chi2(1, result.W)

    return(result)
}

struct _2snardl_wald_result scalar _2snardl_wald_general( ///
    real colvector theta_hat, ///
    real matrix    V_theta, ///
    real matrix    R_sel, ///
    real colvector r)
{
    struct _2snardl_wald_result scalar result
    real colvector diff
    real matrix    meat

    diff = R_sel * theta_hat - r
    meat = R_sel * V_theta * R_sel'
    result.W  = diff' * invsym(meat) * diff
    result.df = rows(R_sel)
    result.p  = 1 - chi2(result.df, result.W)

    return(result)
}

// SR Wald test: accounts for linear variable coefficients in the SR vector
// SR coefficient vector layout:
//   [ECT, L1.Dy, ..., Lp.Dy,
//    D.xpos1, ..., L(q-1).D.xpos_k, D.xneg1, ..., L(q-1).D.xneg_k,
//    D.z1, ..., L(r-1).D.z_m,
//    exog, _cons]
// n_lin_sr = total number of linear variable SR coefficients
struct _2snardl_wald_result scalar _2snardl_wald_sr( ///
    real colvector zeta_hat, ///
    real matrix    V_zeta, ///
    real scalar    p_lag, ///
    real scalar    q_lag, ///
    real scalar    kk, ///
    real scalar    n_lin_sr, ///
    string scalar  test_type)
{
    struct _2snardl_wald_result scalar result

    real scalar n_params, idx_pi_pos_start, idx_pi_neg_start
    real matrix R_sel
    real colvector r
    real matrix block_pos, block_neg
    real scalar ii, jj

    n_params = cols(V_zeta)
    // Positions: 1=ECT, 2..1+p_lag=lagged Dy, then pi_pos, pi_neg, lin_sr, exog, _cons
    idx_pi_pos_start = 1 + p_lag + 1
    idx_pi_neg_start = idx_pi_pos_start + q_lag * kk

    if (test_type == "additive") {
        R_sel = J(kk, 1 + p_lag, 0)
        block_pos = J(kk, q_lag*kk, 0)
        block_neg = J(kk, q_lag*kk, 0)
        for (ii = 1; ii <= kk; ii++) {
            for (jj = 0; jj < q_lag; jj++) {
                block_pos[ii, jj*kk + ii] = 1
                block_neg[ii, jj*kk + ii] = -1
            }
        }
        R_sel = R_sel, block_pos, block_neg
        r = J(kk, 1, 0)
    }
    else if (test_type == "impact") {
        R_sel = J(kk, 1 + p_lag, 0)
        block_pos = J(kk, q_lag*kk, 0)
        block_neg = J(kk, q_lag*kk, 0)
        for (ii = 1; ii <= kk; ii++) {
            block_pos[ii, ii] = 1
            block_neg[ii, ii] = -1
        }
        R_sel = R_sel, block_pos, block_neg
        r = J(kk, 1, 0)
    }
    else {
        R_sel = J(q_lag*kk, 1 + p_lag, 0)
        R_sel = R_sel, I(q_lag*kk), -I(q_lag*kk)
        r = J(q_lag*kk, 1, 0)
    }

    // Pad R_sel with trailing zeros for lin_sr, exog, _cons
    if (cols(R_sel) < n_params) {
        R_sel = R_sel, J(rows(R_sel), n_params - cols(R_sel), 0)
    }

    result = _2snardl_wald_general(zeta_hat, V_zeta, R_sel, r)

    return(result)
}

end


// ============================================================================
// DYNAMIC MULTIPLIER COMPUTATION
// ============================================================================
mata:
mata set matastrict on

real matrix _2snardl_dynmult(real colvector zeta_hat, ///
                              real colvector beta_lr, ///
                              real scalar    p_lag, ///
                              real scalar    q_lag, ///
                              real scalar    kk, ///
                              real scalar    H)
{
    real scalar rho, h, j, idx
    real colvector phi
    real matrix pi_pos, pi_neg
    real matrix mult_pos, mult_neg
    real matrix dy_pos, dy_neg
    real rowvector cum_pos_prev, cum_neg_prev

    rho = zeta_hat[1]

    if (p_lag > 0) {
        phi = zeta_hat[3::2+p_lag]
    }
    else {
        phi = J(0, 1, 0)
    }

    // Extract pi_pos and pi_neg as q x k matrices
    pi_pos = J(q_lag, kk, 0)
    pi_neg = J(q_lag, kk, 0)
    idx = 2 + p_lag + 1
    for (j = 1; j <= q_lag; j++) {
        pi_pos[j, .] = zeta_hat[idx::idx+kk-1]'
        idx = idx + kk
    }
    for (j = 1; j <= q_lag; j++) {
        pi_neg[j, .] = zeta_hat[idx::idx+kk-1]'
        idx = idx + kk
    }

    // Initialize
    dy_pos = J(H, kk, 0)
    dy_neg = J(H, kk, 0)

    for (h = 1; h <= H; h++) {
        if (h <= q_lag) {
            dy_pos[h, .] = dy_pos[h, .] + pi_pos[h, .]
            dy_neg[h, .] = dy_neg[h, .] + pi_neg[h, .]
        }

        for (j = 1; j <= min((p_lag, h-1)); j++) {
            dy_pos[h, .] = dy_pos[h, .] + phi[j] * dy_pos[h-j, .]
            dy_neg[h, .] = dy_neg[h, .] + phi[j] * dy_neg[h-j, .]
        }

        if (h >= 2) {
            cum_pos_prev = colsum(dy_pos[1::h-1, .])
            cum_neg_prev = colsum(dy_neg[1::h-1, .])
            dy_pos[h, .] = dy_pos[h, .] + rho * (cum_pos_prev - beta_lr[1::kk]')
            dy_neg[h, .] = dy_neg[h, .] + rho * (cum_neg_prev - beta_lr[kk+1::2*kk]')
        }
    }

    // Cumulative multipliers
    mult_pos = J(H, kk, 0)
    mult_neg = J(H, kk, 0)
    mult_pos[1, .] = dy_pos[1, .]
    mult_neg[1, .] = dy_neg[1, .]
    for (h = 2; h <= H; h++) {
        mult_pos[h, .] = mult_pos[h-1, .] + dy_pos[h, .]
        mult_neg[h, .] = mult_neg[h-1, .] + dy_neg[h, .]
    }

    return((mult_pos, mult_neg))
}

end


// ============================================================================
// LAG SELECTION (adapted from ardl)
// ============================================================================
mata:
mata set matastrict on

real rowvector _2snardl_optimlag( ///
    real matrix y_data, ///
    real matrix X_data, ///
    real matrix lagcombs, ///
    string scalar ic_type)
{
    real scalar ncombs, T, kk, i
    real scalar ic_val, ic_min, optimcomb
    real scalar sigma2, ll
    real matrix cX, cXX, cXXinv
    real colvector cXy
    real scalar ee

    ncombs = rows(lagcombs)
    T = rows(y_data)

    ic_min = .
    optimcomb = 1

    for (i = 1; i <= ncombs; i++) {
        cX = X_data
        kk = cols(cX)

        cXX    = cross(cX, cX)
        cXXinv = invsym(cXX)
        cXy    = cross(cX, y_data)

        ee = cross(y_data, y_data) - cXy' * cXXinv * cXy
        sigma2 = ee / T
        ll = T * log(2 * pi()) + T * log(sigma2) + T

        if (ic_type == "aic") {
            ic_val = ll + 2 * kk
        }
        else {
            ic_val = ll + kk * log(T)
        }

        if (ic_val < ic_min) {
            ic_min = ic_val
            optimcomb = i
        }
    }

    return(lagcombs[optimcomb, .])
}

end


// ============================================================================
// STATA-FACING WRAPPER FUNCTIONS
// Called from twostep_nardl.ado via single-line mata: statements
// ============================================================================
mata:
mata set matastrict on

// Step 1 wrapper for k=1 (FM-OLS or OLS), with optional linear variables
void _2snardl_run_step1_k1(string scalar depvar, string scalar xposvar, ///
                            string scalar xvar, string scalar xnegvar, ///
                            string scalar linvars, ///
                            string scalar touse, real scalar use_fmols, ///
                            real scalar bw, ///
                            string scalar b_lr_name, string scalar V_lr_name, ///
                            string scalar tau2_name, string scalar alpha_name, ///
                            string scalar ect_name, ///
                            string scalar b_lin_name, string scalar V_lin_name)
{
    struct _2snardl_fmols_result scalar res
    real colvector yy, xp, xx, xn
    real matrix zl

    yy = st_data(., depvar, touse)
    xp = st_data(., xposvar, touse)
    xx = st_data(., xvar, touse)
    xn = st_data(., xnegvar, touse)

    // Load linear variables (may be empty)
    if (strlen(strtrim(linvars)) > 0) {
        zl = st_data(., tokens(linvars), touse)
    }
    else {
        zl = J(rows(yy), 0, 0)
    }

    res = _2snardl_fmols(yy, xp, xx, xn, zl, use_fmols, bw)

    st_matrix(b_lr_name, res.beta_lr')
    st_matrix(V_lr_name, res.V_lr)
    st_numscalar(tau2_name, res.tau2)
    st_numscalar(alpha_name, res.alpha)
    st_store(., ect_name, touse, res.ect)

    // Store linear variable LR coefficients
    if (res.n_lin > 0) {
        st_matrix(b_lin_name, res.beta_lin')
        st_matrix(V_lin_name, res.V_lin)
    }
    else {
        st_matrix(b_lin_name, J(1, 0, 0))
        st_matrix(V_lin_name, J(0, 0, 0))
    }
}

// Step 1 wrapper for k>1 (FM-TOLS or TOLS), with optional linear variables
void _2snardl_run_step1_kn(string scalar depvar, string scalar xposvars, ///
                            string scalar xvars, string scalar xnegvars, ///
                            string scalar linvars, ///
                            string scalar touse, real scalar use_fm, ///
                            real scalar bw, ///
                            string scalar b_lr_name, string scalar V_lr_name, ///
                            string scalar tau2_name, string scalar alpha_name, ///
                            string scalar ect_name, ///
                            string scalar b_lin_name, string scalar V_lin_name)
{
    struct _2snardl_fmtols_result scalar res
    real colvector yy
    real matrix xp, xx, xn, zl

    yy = st_data(., depvar, touse)
    xp = st_data(., tokens(xposvars), touse)
    xx = st_data(., tokens(xvars), touse)
    xn = st_data(., tokens(xnegvars), touse)

    // Load linear variables (may be empty)
    if (strlen(strtrim(linvars)) > 0) {
        zl = st_data(., tokens(linvars), touse)
    }
    else {
        zl = J(rows(yy), 0, 0)
    }

    res = _2snardl_fmtols(yy, xp, xx, xn, zl, use_fm, bw)

    st_matrix(b_lr_name, (res.beta_pos \ res.beta_neg)')
    st_matrix(V_lr_name, res.V_lr)
    st_numscalar(tau2_name, res.tau2)
    st_numscalar(alpha_name, res.rho_vec[1])
    st_store(., ect_name, touse, res.ect)

    // Store linear variable LR coefficients
    if (res.n_lin > 0) {
        st_matrix(b_lin_name, res.beta_lin')
        st_matrix(V_lin_name, res.V_lin)
    }
    else {
        st_matrix(b_lin_name, J(1, 0, 0))
        st_matrix(V_lin_name, J(0, 0, 0))
    }
}

// LR Wald test wrapper for k=1
void _2snardl_run_wald_lr_k1(string scalar b_lr_name, string scalar V_lr_name, ///
                              string scalar W_name, string scalar p_name)
{
    struct _2snardl_wald_result scalar wres
    real scalar lam, Vlam

    lam  = st_matrix(b_lr_name)[1,1] - st_matrix(b_lr_name)[1,2]
    Vlam = (1, -1) * st_matrix(V_lr_name) * (1 \ -1)

    wres = _2snardl_wald_lr_k1(lam, Vlam, 0)

    st_numscalar(W_name, wres.W)
    st_numscalar(p_name, wres.p)
}

// LR Wald test wrapper for k>1
void _2snardl_run_wald_lr_kn(string scalar b_lr_name, string scalar V_lr_name, ///
                              real scalar kk, ///
                              string scalar W_name, string scalar p_name)
{
    struct _2snardl_wald_result scalar wres
    real colvector bv
    real matrix Vm, Rsel
    real colvector rv

    bv  = st_matrix(b_lr_name)'
    Vm  = st_matrix(V_lr_name)
    Rsel = I(kk), -I(kk)
    rv  = J(kk, 1, 0)

    wres = _2snardl_wald_general(bv, Vm, Rsel, rv)

    st_numscalar(W_name, wres.W)
    st_numscalar(p_name, wres.p)
}

// SR Wald test wrapper (updated to pass n_lin_sr)
void _2snardl_run_wald_sr(string scalar b_sr_name, string scalar V_sr_name, ///
                           real scalar p_lag, real scalar q_lag, real scalar kk, ///
                           real scalar n_lin_sr, ///
                           string scalar test_type, ///
                           string scalar W_name, string scalar p_name)
{
    struct _2snardl_wald_result scalar wres
    real colvector zhat
    real matrix Vz

    zhat = st_matrix(b_sr_name)'
    Vz   = st_matrix(V_sr_name)

    wres = _2snardl_wald_sr(zhat, Vz, p_lag, q_lag, kk, n_lin_sr, test_type)

    st_numscalar(W_name, wres.W)
    st_numscalar(p_name, wres.p)
}

// Dynamic multiplier wrapper
void _2snardl_run_dynmult(string scalar b_sr_name, string scalar b_lr_name, ///
                           real scalar p_lag, real scalar q_lag, ///
                           real scalar kk, real scalar H, ///
                           string scalar result_name)
{
    real colvector zeta, blr
    real matrix mult

    zeta = st_matrix(b_sr_name)'
    blr  = st_matrix(b_lr_name)'

    mult = _2snardl_dynmult(zeta, blr, p_lag, q_lag, kk, H)

    st_matrix(result_name, mult)
}

end