*! regproject v1.0.1
*! Post-estimation projection and boundary analysis
*! Author : Dr Noman Arshed, Senior Lecturer
*!          Department of Business Analytics
*!          Sunway Business School, Sunway University
*! Email  : nouman.arshed@gmail.com
*! GitHub : nomanarshed/regproject
*! Date   : April 2026

program define regproject
    version 14

    /* ------------------------------------------------------------------ */
    /*  SYNTAX                                                              */
    /* ------------------------------------------------------------------ */
    syntax varname [,                   ///
        IVMin(real 99999999)            ///
        IVMax(real -99999999)           ///
        IVMins(numlist)                 ///
        IVMaxs(numlist)                 ///
        YMin(real 99999999)             ///
        YMax(real -99999999)            ///
        SAving(string)                  ///
        COMBINE                         ///
        NODisplay                       ///
    ]

    local focusvar `varlist'

    /* ------------------------------------------------------------------ */
    /*  BLOCK 1 — POST-ESTIMATION CHECKS                                   */
    /* ------------------------------------------------------------------ */
    if "`e(cmd)'" == "" {
        di as error "No estimation results found. Run a regression first."
        exit 301
    }

    local supported_cmds "regress xtreg xtregress qreg iqreg xtregar newey arima ardl ivregress"
    local ecmd = e(cmd)
    local cmd_ok = 0
    foreach c of local supported_cmds {
        if "`ecmd'" == "`c'" local cmd_ok = 1
    }
    if `cmd_ok' == 0 {
        di as error "regproject does not currently support e(cmd) = `ecmd'."
        di as error "Supported: `supported_cmds'"
        exit 322
    }

    capture confirm variable `focusvar'
    if _rc {
        di as error "Variable `focusvar' not found in dataset."
        exit 111
    }

    matrix _eb = e(b)
    local colnames : colnames _eb

    local focusfound = 0
    local focuspos   = 0
    local regressors ""

    foreach v of local colnames {
        if "`v'" == "_cons" continue
        /* strip equation prefix if present (e.g. "y:x1" -> "x1") */
        local vclean = "`v'"
        if strpos("`v'", ":") > 0 {
            local vclean = substr("`v'", strpos("`v'", ":") + 1, .)
        }
        local regressors `regressors' `vclean'
        if "`vclean'" == "`focusvar'" {
            local focusfound = 1
            local focuspos   = wordcount("`regressors'")
        }
    }

    if `focusfound' == 0 {
        di as error "`focusvar' not found in the estimated coefficients."
        exit 111
    }

    local nreg = wordcount("`regressors'")

    /* ------------------------------------------------------------------ */
    /*  BLOCK 2 — OPTION CONFLICT CHECKS                                   */
    /* ------------------------------------------------------------------ */
    local has_ivmin  = (`ivmin'  !=  99999999)
    local has_ivmax  = (`ivmax'  != -99999999)
    local has_ivmins = ("`ivmins'" != "")
    local has_ivmaxs = ("`ivmaxs'" != "")
    local has_ymin   = (`ymin'   !=  99999999)
    local has_ymax   = (`ymax'   != -99999999)

    if (`has_ivmin' | `has_ivmax') & (`has_ivmins' | `has_ivmaxs') {
        di as error "Cannot specify both ivmin()/ivmax() and ivmins()/ivmaxs()."
        exit 198
    }

    if `has_ivmins' {
        local nmins = wordcount("`ivmins'")
        if `nmins' != `nreg' {
            di as error "ivmins() has `nmins' value(s) but model has `nreg' regressor(s)."
            exit 198
        }
    }
    if `has_ivmaxs' {
        local nmaxs = wordcount("`ivmaxs'")
        if `nmaxs' != `nreg' {
            di as error "ivmaxs() has `nmaxs' value(s) but model has `nreg' regressor(s)."
            exit 198
        }
    }

    /* ------------------------------------------------------------------ */
    /*  BLOCK 3 — DATA MODE DETECTION                                      */
    /* ------------------------------------------------------------------ */
    local datamode ""
    local panelvar ""
    local timevar  ""

    capture quietly xtset
    if _rc == 0 {
        local panelvar "`r(panelvar)'"
        local timevar  "`r(timevar)'"
        if "`panelvar'" != "" & "`timevar'" != "" {
            local datamode "panel"
        }
        else if "`timevar'" != "" {
            local datamode "timeseries"
        }
    }

    if "`datamode'" == "" {
        capture quietly tsset
        if _rc == 0 {
            local timevar "`r(timevar)'"
            if "`timevar'" != "" local datamode "timeseries"
        }
    }

    if "`datamode'" == "" local datamode "crosssection"

    /* ------------------------------------------------------------------ */
    /*  BLOCK 4 — BUILD BOUNDS MATRIX                                      */
    /* ------------------------------------------------------------------ */
    tempname bounds_lower bounds_upper
    matrix `bounds_lower' = J(1, `nreg', 0)
    matrix `bounds_upper' = J(1, `nreg', 0)
    local bounds_source ""

    local kk = 0
    foreach v of local regressors {
        local ++kk

        if strpos("`v'", ".") > 0 {
            quietly summarize `focusvar', meanonly
            matrix `bounds_lower'[1, `kk'] = r(min)
            matrix `bounds_upper'[1, `kk'] = r(max)
            local bounds_source `bounds_source' "factor"
            continue
        }

        quietly summarize `v', meanonly
        local dmin_v = r(min)
        local dmax_v = r(max)

        if `kk' == `focuspos' {
            if `has_ivmin' {
                matrix `bounds_lower'[1, `kk'] = `ivmin'
                local src_k "ivmin/ivmax"
            }
            else if `has_ivmins' {
                local lb_k : word `kk' of `ivmins'
                matrix `bounds_lower'[1, `kk'] = `lb_k'
                local src_k "ivmins/ivmaxs"
            }
            else {
                matrix `bounds_lower'[1, `kk'] = `dmin_v'
                local src_k "data range"
            }

            if `has_ivmax' {
                matrix `bounds_upper'[1, `kk'] = `ivmax'
            }
            else if `has_ivmaxs' {
                local ub_k : word `kk' of `ivmaxs'
                matrix `bounds_upper'[1, `kk'] = `ub_k'
            }
            else {
                matrix `bounds_upper'[1, `kk'] = `dmax_v'
            }
        }
        else {
            if `has_ivmins' {
                local lb_k : word `kk' of `ivmins'
                matrix `bounds_lower'[1, `kk'] = `lb_k'
                local src_k "ivmins/ivmaxs"
            }
            else {
                matrix `bounds_lower'[1, `kk'] = `dmin_v'
                local src_k "data range"
            }

            if `has_ivmaxs' {
                local ub_k : word `kk' of `ivmaxs'
                matrix `bounds_upper'[1, `kk'] = `ub_k'
            }
            else {
                matrix `bounds_upper'[1, `kk'] = `dmax_v'
            }
        }

        local bounds_source `bounds_source' "`src_k'"
    }

    /* ------------------------------------------------------------------ */
    /*  BLOCK 5 — COMPUTE MEDIANS AND LATEST VALUES                        */
    /* ------------------------------------------------------------------ */
    tempname med_vec
    matrix `med_vec' = J(1, `nreg', 0)

    local kk = 0
    foreach v of local regressors {
        local ++kk
        quietly summarize `v', detail
        matrix `med_vec'[1, `kk'] = r(p50)
    }

    if "`datamode'" == "timeseries" | "`datamode'" == "panel" {
        quietly summarize `timevar', meanonly
        local latest_t = r(max)
    }

    /* ------------------------------------------------------------------ */
    /*  BLOCK 6 — EXTRACT REGRESSION COEFFICIENTS                          */
    /* ------------------------------------------------------------------ */
    local depvar   = e(depvar)
    local cons_val = 0
    capture local cons_val = _b[_cons]

    /* ------------------------------------------------------------------ */
    /*  BLOCK 7 — PRINT SPECIFICATION SUMMARY TABLE                        */
    /* ------------------------------------------------------------------ */
    di ""
    di as text "{hline 69}"
    di as text "  {bf:regproject} — Projection Specification Summary"
    di as text "{hline 69}"
    di as text "  Model        : " as result "`e(cmd)'" _col(40) as text "(" as result "`e(title)'" as text ")"

    if "`datamode'" == "panel" {
        di as text "  Data mode    : " as result "Panel data" ///
           _col(40) as text "(xtset: " as result "`panelvar'" as text ", " as result "`timevar'" as text ")"
    }
    else if "`datamode'" == "timeseries" {
        di as text "  Data mode    : " as result "Time series" ///
           _col(40) as text "(tsset/xtset: " as result "`timevar'" as text ")"
    }
    else {
        di as text "  Data mode    : " as result "Cross-sectional"
    }

    if `has_ymin' & `has_ymax' {
        local ybound_disp "[`=string(`ymin', "%9.3f")', `=string(`ymax', "%9.3f")']  (user-supplied)"
    }
    else if `has_ymax' & !`has_ymin' {
        local ybound_disp "[data min, `=string(`ymax', "%9.3f")']  (partial — upper only)"
    }
    else if `has_ymin' & !`has_ymax' {
        local ybound_disp "[`=string(`ymin', "%9.3f")', data max]  (partial — lower only)"
    }
    else {
        local ybound_disp "not specified — shading and crossing detection disabled"
    }
    di as text "  Dep. variable: " as result "`depvar'" _col(40) as text "Bounds: " as result "`ybound_disp'"
    di as text "  Focal IV     : " as result "`focusvar'"
    di as text "{hline 69}"
    di as text "  {bf:Variable}" _col(18) "{bf:Coef.}" _col(28) "{bf:Lower}" ///
               _col(38) "{bf:Upper}" _col(50) "{bf:Source}"
    di as text "{hline 69}"

    local kk = 0
    foreach v of local regressors {
        local ++kk
        local coef_k = _b[`v']
        local lb_k   = `bounds_lower'[1, `kk']
        local ub_k   = `bounds_upper'[1, `kk']
        local src_k  : word `kk' of `bounds_source'

        if "`v'" == "`focusvar'" {
            di as result "  `v' (focal)" _col(18) %8.4f `coef_k' ///
               _col(28) %8.3f `lb_k' _col(38) %8.3f `ub_k' ///
               _col(50) as text "`src_k'  {bf:←}"
        }
        else {
            di as result "  `v'" _col(18) %8.4f `coef_k' ///
               _col(28) %8.3f `lb_k' _col(38) %8.3f `ub_k' ///
               _col(50) as text "`src_k'"
        }
    }

    di as text "  _cons" _col(18) %8.4f `cons_val' _col(28) "—" _col(38) "—" _col(50) "—"
    di as text "{hline 69}"

    if "`datamode'" == "timeseries" local ngraphs = 3
    else                            local ngraphs = 4

    di as text "  Graphs       : " as result "`ngraphs'" as text " (`datamode' mode)"
    if "`saving'" != "" {
        di as text "  Saving       : " as result "`saving'_1.gph" as text " ... " as result "`saving'_`ngraphs'.gph"
    }
    else {
        di as text "  Saving       : not specified"
    }
    di as text "{hline 69}"
    di ""

    /* ------------------------------------------------------------------ */
    /*  BLOCK 8 — PRESERVE e() THEN ROUTE TO GRAPH MODULES                */
    /*  Auxiliary regressions in sub-programs overwrite e().              */
    /*  We store here and restore after so repeated calls work.           */
    /* ------------------------------------------------------------------ */
    capture estimates drop _regproject_stored
    estimates store _regproject_stored

    if "`datamode'" == "crosssection" {
        _regproject_cs `focusvar', regressors(`regressors')           ///
            nreg(`nreg') focuspos(`focuspos') depvar(`depvar')        ///
            cons(`cons_val')                                           ///
            bounds_lower(`bounds_lower') bounds_upper(`bounds_upper') ///
            med_vec(`med_vec')                                         ///
            has_ymin(`has_ymin') has_ymax(`has_ymax')                 ///
            ymin_val(`ymin') ymax_val(`ymax')                         ///
            saving(`saving') `combine' `nodisplay'
    }
    else if "`datamode'" == "panel" {
        _regproject_pan `focusvar', regressors(`regressors')           ///
            nreg(`nreg') focuspos(`focuspos') depvar(`depvar')         ///
            cons(`cons_val') panelvar(`panelvar') timevar(`timevar')   ///
            latest_t(`latest_t')                                       ///
            bounds_lower(`bounds_lower') bounds_upper(`bounds_upper')  ///
            med_vec(`med_vec')                                         ///
            has_ymin(`has_ymin') has_ymax(`has_ymax')                  ///
            ymin_val(`ymin') ymax_val(`ymax')                          ///
            saving(`saving') `combine' `nodisplay'
    }
    else if "`datamode'" == "timeseries" {
        _regproject_ts `focusvar', regressors(`regressors')            ///
            nreg(`nreg') focuspos(`focuspos') depvar(`depvar')         ///
            cons(`cons_val') timevar(`timevar') latest_t(`latest_t')   ///
            bounds_lower(`bounds_lower') bounds_upper(`bounds_upper')  ///
            med_vec(`med_vec')                                         ///
            has_ymin(`has_ymin') has_ymax(`has_ymax')                  ///
            ymin_val(`ymin') ymax_val(`ymax')                          ///
            saving(`saving') `combine' `nodisplay'
    }

    /* restore original estimation results for the user */
    quietly estimates restore _regproject_stored
    quietly estimates drop    _regproject_stored

end