*! estat weights subcommand for trop

/*==============================================================================
  trop_estat_weights

  Display descriptive statistics for TROP weights (Time and Unit).

  Syntax:
    estat weights [, heatmap detailed]

  Options:
    heatmap  -  Display a heatmap of the weight matrix.
    detailed -  Display detailed percentiles of the weight vectors.

  Description:
    Reports summary statistics for the estimated weights.

    For the Two-Step estimator, weights are observation-specific.
    This command reports statistics for the weights associated with the
    first treated observation. Two-Step weights consist of:
      theta (T x 1): Time weights
      omega (N x 1): Unit weights

    For the Joint estimator, weights are global across observations:
      delta_time (T x 1): Global time weights
      delta_unit (N x 1): Global unit weights
==============================================================================*/

program define trop_estat_weights
    version 17
    syntax [, heatmap detailed]

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

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

    // Execute method-specific display
    if "`method'" == "twostep" {
        _estat_weights_twostep
    }
    else if "`method'" == "joint" {
        _estat_weights_joint
    }
    else {
        _estat_weights_generic
    }

    // Display heatmap if requested
    if "`heatmap'" != "" {
        _graph_weight_heatmap
    }

    // Display detailed percentiles if requested
    if "`detailed'" != "" {
        _estat_weights_detailed
    }
end

/*==============================================================================
  Display weights for Two-Step estimator

  Displays summary statistics (Mean, Std. Dev., Min, Max) for theta and omega.
  As weights vary by treated observation, statistics are reported for the
  first treated observation.
==============================================================================*/
program define _estat_weights_twostep
    di as txt ""
    di as txt "Weight Distribution (Twostep; Algorithm 2 default)"
    di as txt "{hline 78}"
    di as txt "Note: Weights vary by treated observation. Showing statistics"
    di as txt "      for first treated observation."
    di as txt ""

    // Process time weights (theta)
    capture confirm matrix e(theta)
    local has_theta = (_rc == 0)

    capture confirm matrix e(omega)
    local has_omega = (_rc == 0)

    if `has_theta' {
        tempname theta
        matrix `theta' = e(theta)
        local T = rowsof(`theta')

        di as txt "Time weights (theta):"
        mata: _display_weight_vector_stats("e(theta)", "time")
    }
    else {
        di as txt "Time weights (theta): (not available)"
    }

    di as txt ""

    // Process unit weights (omega)
    if `has_omega' {
        tempname omega
        matrix `omega' = e(omega)
        local N = rowsof(`omega')

        di as txt "Unit weights (omega):"
        mata: _display_weight_vector_stats("e(omega)", "unit")
    }
    else {
        di as txt "Unit weights (omega): (not available)"
    }

    di as txt "{hline 78}"
end

/*==============================================================================
  Display weights for Joint estimator

  Displays summary statistics for delta_time and delta_unit.
  Weights are global in the Joint estimator.
==============================================================================*/
program define _estat_weights_joint
    di as txt ""
    di as txt "Weight Distribution (Joint; shared-tau extension)"
    di as txt "{hline 78}"

    capture confirm matrix e(delta_time)
    local has_delta_time = (_rc == 0)

    capture confirm matrix e(delta_unit)
    local has_delta_unit = (_rc == 0)

    // Global time weights
    di as txt "Global time weights (delta_time):"
    if `has_delta_time' {
        mata: _display_weight_vector_stats("e(delta_time)", "time")
    }
    else {
        di as txt "  (not available)"
    }

    di as txt ""

    // Global unit weights
    di as txt "Global unit weights (delta_unit):"
    if `has_delta_unit' {
        mata: _display_weight_vector_stats("e(delta_unit)", "unit")
    }
    else {
        di as txt "  (not available)"
    }

    di as txt "{hline 78}"
end

/*==============================================================================
  Generic weight display

  Displays summary statistics for available weight vectors when the
  estimation method is not explicitly identified.
==============================================================================*/
program define _estat_weights_generic
    di as txt ""
    di as txt "Weight Distribution"
    di as txt "{hline 78}"

    capture confirm matrix e(theta)
    if !_rc {
        di as txt "Time weights:"
        mata: _display_weight_vector_stats("e(theta)", "time")
        di as txt ""
    }

    capture confirm matrix e(omega)
    if !_rc {
        di as txt "Unit weights:"
        mata: _display_weight_vector_stats("e(omega)", "unit")
    }

    di as txt "{hline 78}"
end

/*==============================================================================
  Weight Heatmap

  Generates a 2D heatmap of the T x N weight matrix using twoway contour.
  If e(W_mat) is available, it is used directly.
  Otherwise, an outer-product approximation is constructed:
    - Twostep: theta * omega'
    - Joint: delta_time * delta_unit'
  Uses Stata built-in twoway contour (no external packages required).
==============================================================================*/
program define _graph_weight_heatmap

    // Determine method and construct weight matrix in Mata
    local method = "`e(method)'"
    if "`method'" == "" {
        local method = "twostep"
    }

    // Check availability of weight vectors
    local can_plot = 0
    local title_suffix = ""

    // First check for pre-stored full weight matrix
    capture confirm matrix e(W_mat)
    if !_rc {
        local can_plot = 1
        local title_suffix = ""
        tempname W_heatmap
        matrix `W_heatmap' = e(W_mat)
    }
    else {
        // Reconstruct from vectors via outer product
        if "`method'" == "twostep" {
            capture confirm matrix e(theta)
            if !_rc {
                capture confirm matrix e(omega)
                if !_rc {
                    local can_plot = 1
                    local title_suffix = " (theta x omega)"
                    tempname W_heatmap
                    mata: st_matrix("`W_heatmap'", ///
                        _trop_heatmap_outer("e(theta)", "e(omega)"))
                }
            }
        }
        else {
            capture confirm matrix e(delta_time)
            if !_rc {
                capture confirm matrix e(delta_unit)
                if !_rc {
                    local can_plot = 1
                    local title_suffix = " (delta_time x delta_unit)"
                    tempname W_heatmap
                    mata: st_matrix("`W_heatmap'", ///
                        _trop_heatmap_outer("e(delta_time)", "e(delta_unit)"))
                }
            }
        }
    }

    if !`can_plot' {
        di as error "Cannot generate heatmap: weight matrices not available"
        exit 111
    }

    // Get matrix dimensions
    local T = rowsof(`W_heatmap')
    local N = colsof(`W_heatmap')
    local total = `T' * `N'

    // Expand matrix to long format for twoway contour
    preserve
    quietly {
        clear
        set obs `total'
        gen double _period = .
        gen double _unit = .
        gen double _weight = .

        local obs = 0
        forvalues t = 1/`T' {
            forvalues i = 1/`N' {
                local ++obs
                replace _period = `t' in `obs'
                replace _unit = `i' in `obs'
                replace _weight = `W_heatmap'[`t', `i'] in `obs'
            }
        }
    }

    // Generate heatmap using twoway contour
    local gtitle "Weight Heatmap`title_suffix'"
    twoway (contour _weight _period _unit, ///
        ccolors(white*0.1 yellow*0.5 orange*0.7 red*0.9 red)), ///
        xtitle("Time Period") ytitle("Unit") ///
        title("`gtitle'") ///
        name(_trop_weight_heatmap, replace)

    restore
end

/*==============================================================================
  Detailed Weight Distribution

  Displays percentiles (10, 25, 50, 75, 90) for weight vectors.
==============================================================================*/
program define _estat_weights_detailed
    di as txt ""
    di as txt "Detailed weight distribution (percentiles):"
    di as txt ""

    // Time weights
    capture confirm matrix e(theta)
    if !_rc {
        di as txt "Time weights percentiles:"
        mata: _display_weight_percentiles("e(theta)")
        di as txt ""
    }
    else {
        capture confirm matrix e(delta_time)
        if !_rc {
            di as txt "Time weights percentiles:"
            mata: _display_weight_percentiles("e(delta_time)")
            di as txt ""
        }
    }

    // Unit weights
    capture confirm matrix e(omega)
    if !_rc {
        di as txt "Unit weights percentiles:"
        mata: _display_weight_percentiles("e(omega)")
    }
    else {
        capture confirm matrix e(delta_unit)
        if !_rc {
            di as txt "Unit weights percentiles:"
            mata: _display_weight_percentiles("e(delta_unit)")
        }
    }
end

/*==============================================================================
  Mata Helper Functions
==============================================================================*/

version 17
mata:
mata set matastrict on

/*------------------------------------------------------------------------------
  _trop_heatmap_outer()

  Computes the outer product of two weight vectors for heatmap visualization.
  Ensures both vectors are column-oriented before computing the product.

  Arguments:
    matname_t - Name of the Stata matrix for time weights (T x 1)
    matname_u - Name of the Stata matrix for unit weights (N x 1)

  Returns:
    T x N matrix (outer product)
------------------------------------------------------------------------------*/
real matrix _trop_heatmap_outer(string scalar matname_t, string scalar matname_u)
{
    real colvector t_vec, u_vec

    t_vec = st_matrix(matname_t)
    u_vec = st_matrix(matname_u)

    // Ensure column orientation
    if (cols(t_vec) > rows(t_vec)) {
        t_vec = t_vec'
    }
    if (cols(u_vec) > rows(u_vec)) {
        u_vec = u_vec'
    }

    return(t_vec * u_vec')
}

/*------------------------------------------------------------------------------
  _display_weight_vector_stats()

  Displays summary statistics (Mean, Std. Dev., Min, Max) for a weight vector.
  Also reports the index (time or unit) of the minimum and maximum values.

  Arguments:
    matname  - Name of the Stata matrix (e.g., "e(theta)")
    wtype    - "time" or "unit" (determines index labeling)
------------------------------------------------------------------------------*/
void _display_weight_vector_stats(string scalar matname, string scalar wtype)
{
    real colvector w
    real scalar n, min_val, max_val, mean_val, sd_val
    real scalar min_idx, max_idx, i

    w = st_matrix(matname)

    // Ensure column orientation
    if (cols(w) > rows(w)) {
        w = w'
    }

    n = rows(w)
    min_val = min(w)
    max_val = max(w)
    mean_val = mean(w)
    sd_val = sqrt(variance(w))

    // Find index of first occurrence of min and max
    min_idx = .
    max_idx = .
    for (i=1; i<=n; i++) {
        if (w[i] == min_val & min_idx == .) min_idx = i
        if (w[i] == max_val & max_idx == .) max_idx = i
    }

    printf("{txt}  Mean     = {res}%10.4f\n", mean_val)
    printf("{txt}  Std.Dev. = {res}%10.4f\n", sd_val)

    if (wtype == "time") {
        printf("{txt}  Min      = {res}%10.4f{txt} (t={res}%g{txt})\n", min_val, min_idx)
        printf("{txt}  Max      = {res}%10.4f{txt} (t={res}%g{txt})\n", max_val, max_idx)
    }
    else {
        printf("{txt}  Min      = {res}%10.4f{txt} (i={res}%g{txt})\n", min_val, min_idx)
        printf("{txt}  Max      = {res}%10.4f{txt} (i={res}%g{txt})\n", max_val, max_idx)
    }
}

/*------------------------------------------------------------------------------
  _display_weight_percentiles()

  Displays the 10th, 25th, 50th, 75th, and 90th percentiles of a weight vector.

  Arguments:
    matname - Name of the Stata matrix
------------------------------------------------------------------------------*/
void _display_weight_percentiles(string scalar matname)
{
    real colvector w, sorted_w
    real scalar n, p10, p25, p50, p75, p90

    w = st_matrix(matname)

    // Ensure column orientation
    if (cols(w) > rows(w)) {
        w = w'
    }

    n = rows(w)
    sorted_w = sort(w, 1)

    p10 = _trop_interpolate_percentile(sorted_w, 0.10)
    p25 = _trop_interpolate_percentile(sorted_w, 0.25)
    p50 = _trop_interpolate_percentile(sorted_w, 0.50)
    p75 = _trop_interpolate_percentile(sorted_w, 0.75)
    p90 = _trop_interpolate_percentile(sorted_w, 0.90)

    printf("{txt}  10%%      = {res}%10.4f\n", p10)
    printf("{txt}  25%%      = {res}%10.4f\n", p25)
    printf("{txt}  50%%      = {res}%10.4f\n", p50)
    printf("{txt}  75%%      = {res}%10.4f\n", p75)
    printf("{txt}  90%%      = {res}%10.4f\n", p90)
}

end