*! trop_predict_validate -- data consistency checks for postestimation

/*
    Verify that the dataset in memory is consistent with the estimation
    sample before computing postestimation predictions.

    Three validation levels are applied in sequence:

      1  (fast)        e(cmd)=="trop" and e(N_obs)==_N
      2  (structural)  panel dimensions (N, T) match e(N_units), e(N_periods)
      3  (strict)      datasignature hash comparison; requires -robust_check-

    Exit codes:
      301  no trop estimation results in memory
      459  sample size or panel structure mismatch
*/


program define trop_predict_validate
    version 17
    syntax, [RObust_check]

    // ---- level 1: estimation results and observation count ---------------

    if "`e(cmd)'" != "trop" {
        di as error "last estimates not found"
        exit 301
    }

    local n_obs = e(N_obs)
    if `n_obs' != . & `n_obs' != _N {
        di as error "{hline}"
        di as error "sample size changed"
        di as error "  estimation sample: e(N_obs) = `n_obs'"
        di as error "  current dataset:   _N = " _N
        di as error ""
        di as error "restore the estimation sample or rerun {bf:trop}"
        di as error "{hline}"
        exit 459
    }

    // ---- level 2: panel dimensions (N units, T periods) -----------------

    local N_stored = e(N_units)
    local T_stored = e(N_periods)

    if `N_stored' != . & `T_stored' != . {
        local panelvar "`e(panelvar)'"
        local timevar  "`e(timevar)'"

        if "`panelvar'" != "" & "`timevar'" != "" {
            qui {
                // count distinct units and periods within e(sample)
                tempvar panel_id time_id
                egen `panel_id' = group(`panelvar') if e(sample)
                egen `time_id'  = group(`timevar')  if e(sample)

                sum `panel_id' if e(sample), meanonly
                local N_current = r(max)

                sum `time_id' if e(sample), meanonly
                local T_current = r(max)
            }

            if `N_current' != `N_stored' | `T_current' != `T_stored' {
                di as error "{hline}"
                di as error "panel structure changed"
                di as error "  estimation: N = `N_stored', T = `T_stored'"
                di as error "  current:    N = `N_current', T = `T_current'"
                di as error ""
                di as error "rerun {bf:trop} on the current dataset"
                di as error "{hline}"
                exit 459
            }
        }
    }

    // ---- level 3: datasignature hash (robust_check only) ----------------

    if "`robust_check'" != "" {
        local stored_sig "`e(data_signature)'"

        if "`stored_sig'" != "" {
            qui datasignature
            local current_sig "`r(datasignature)'"

            // datasignature format: N:k(bytes):hash1:hash2
            // compare hash fields only; the byte-count field may change
            // after -compress- or -recast- without altering values
            tokenize "`stored_sig'", parse(":")
            local stored_hash "`5':`7'"
            tokenize "`current_sig'", parse(":")
            local current_hash "`5':`7'"

            if "`stored_hash'" != "`current_hash'" {
                di as error "{hline}"
                di as error "data signature mismatch (robust_check)"
                di as error "  stored:  `stored_sig'"
                di as error "  current: `current_sig'"
                di as error ""
                di as error "omit {bf:robust_check} to skip hash verification"
                di as error "{hline}"
                exit 459
            }
        }
    }
end