*! Predict treatment effects after trop estimation

/*
    Post-estimation command: generates predicted treatment effects for
    treated observations.

    Two-step method:
        Retrieves the observation-specific estimates tau_{it} stored in e(tau),
        where tau_{it} = Y_{it} - alpha_i - beta_t - L_{it}. Each treated cell
        receives its own estimate, permitting heterogeneous effects across
        units and time periods.

    Joint method:
        Assigns the scalar ATT estimate to every treated observation, under
        the maintained assumption of a homogeneous treatment effect.

    Control observations receive missing values.
*/


program define trop_predict_te
    version 17
    syntax newvarname [if] [in]

    marksample touse, novarlist


    // Retrieve estimation results.
    local treatvar "`e(treatvar)'"
    if "`treatvar'" == "" {
        di as error "e(treatvar) is missing"
        exit 498
    }

    local depvar "`e(depvar)'"
    if "`depvar'" == "" {
        di as error "e(depvar) is missing"
        exit 498
    }

    local method "`e(method)'"
    if "`method'" == "" {
        local method "twostep"
    }

    qui gen double `varlist' = .

    if "`method'" == "twostep" {
        // Map observation-specific tau_{it} from e(tau) back to the dataset.
        mata: _trop_predict_te_twostep("`varlist'", "`e(panelvar)'", "`e(timevar)'", "`treatvar'", "`touse'")
    }
    else {
        // Assign the homogeneous ATT to all treated cells.
        local att = e(att)
        if missing(`att') {
            di as error "e(att) is missing"
            exit 498
        }
        qui replace `varlist' = `att' if `treatvar' == 1 & `touse'
    }

    if "`method'" == "joint" {
        label variable `varlist' "Treatment effect (shared tau / ATT)"
    }
    else {
        label variable `varlist' "Treatment effect (cell-specific tau_it)"
    }
end


version 17
mata:
mata set matastrict on

/*
    _trop_predict_te_twostep()

    Maps the N_treated x 1 vector e(tau) to the corresponding observations
    in the current dataset. The vector is ordered by (time, unit) within the
    estimation sample; this function reconstructs that ordering to assign
    each tau_{it} to the correct row.
*/
void _trop_predict_te_twostep(
    string scalar varname,
    string scalar panelvar,
    string scalar timevar,
    string scalar treatvar,
    string scalar touse_var
)
{
    real matrix tau_vec
    real colvector panel_idx, time_idx, treat, pred, obs_order
    real scalar n, k, tau_k, i, n_tau, obs_i
    real matrix treated_info
    real scalar n_treated

    tau_vec = st_matrix("e(tau)")
    if (rows(tau_vec) == 0) {
        errprintf("e(tau) is empty or missing\n")
        return
    }
    n_tau = rows(tau_vec)

    // Build integer group indices for panel and time within e(sample).
    {
        string scalar temp_pidx, temp_tidx

        temp_pidx = st_tempname()
        temp_tidx = st_tempname()

        stata("qui egen " + temp_pidx + " = group(" + panelvar + ") if e(sample)")
        stata("qui egen " + temp_tidx + " = group(" + timevar + ") if e(sample)")

        panel_idx = st_data(., temp_pidx, touse_var)
        time_idx = st_data(., temp_tidx, touse_var)
        treat = st_data(., treatvar, touse_var)

        stata("capture drop " + temp_pidx)
        stata("capture drop " + temp_tidx)
    }

    n = rows(panel_idx)

    // Count treated observations with valid indices.
    n_treated = 0
    for (i = 1; i <= n; i++) {
        if (treat[i] == 1 && panel_idx[i] < . && time_idx[i] < .) {
            n_treated++
        }
    }

    if (n_treated == 0 || n_treated != n_tau) {
        if (n_treated != n_tau) {
            printf("{txt}(note: treated obs count %g != e(tau) length %g; falling back to Y-Y(0))\n",
                   n_treated, n_tau)
            _trop_predict_te_fallback(varname, touse_var)
            return
        }
        return
    }

    // Collect (time_idx, panel_idx, row_index) for treated cells.
    treated_info = J(n_treated, 3, .)
    k = 0
    for (i = 1; i <= n; i++) {
        if (treat[i] == 1 && panel_idx[i] < . && time_idx[i] < .) {
            k++
            treated_info[k, 1] = time_idx[i]
            treated_info[k, 2] = panel_idx[i]
            treated_info[k, 3] = i
        }
    }

    // Sort by (time, unit) to match the ordering convention in e(tau).
    treated_info = sort(treated_info, (1, 2))

    // Assign tau_{it} values to the corresponding rows.
    pred = J(n, 1, .)
    for (k = 1; k <= n_treated; k++) {
        obs_i = treated_info[k, 3]
        pred[obs_i] = tau_vec[k, 1]
    }

    st_store(., varname, touse_var, pred)
}


/*
    _trop_predict_te_fallback()

    Fallback when e(tau) cannot be matched to the current data. Computes
    tau_{it} = Y_{it} - Yhat_{it}(0) from the stored parameter estimates.
*/
void _trop_predict_te_fallback(
    string scalar varname,
    string scalar touse_var
)
{
    string scalar tmpname

    printf("{txt}(note: calculating treatment effects from parameter estimates)\n")

    tmpname = st_tempname()
    stata("qui trop_predict_y0 " + tmpname + " if " + touse_var)
    stata("qui replace " + varname + " = " + st_global("e(depvar)") +
          " - " + tmpname + " if " + st_global("e(treatvar)") + " == 1 & " + touse_var)
    stata("capture drop " + tmpname)
}

end