import os
"""
Projet 8a — Laboratoire d'Intelligence Économétrique Africaine
Tech : Dash + Plotly + statsmodels · Données WDI réelles
Port : 8055
"""

import sys, os
import dash
from dash import dcc, html, Input, Output, State
import dash_bootstrap_components as dbc
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import statsmodels.api as sm
from statsmodels.tsa.stattools import grangercausalitytests, adfuller
from statsmodels.tsa.api import VAR
from statsmodels.stats.diagnostic import recursive_olsresiduals
from data_engine import get_econometric_data

# ─── App Init ────────────────────────────────────────────────────────
app = dash.Dash(
    __name__,
    external_stylesheets=[dbc.themes.DARKLY],
    title="Econometric Lab · N'DRI Research",
    suppress_callback_exceptions=True,
    meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}],
)

COLORS = {
    "dark_bg": "#070d1a", "card_bg": "#0d1a2e", "border": "#1a3a5c",
    "teal": "#0e9aa7", "blue": "#1a6fa5", "gold": "#e8a020",
    "text": "#c8d8ea", "muted": "#4a6a85",
}

df = get_econometric_data()

# All numeric variables available in the data
VAR_OPTIONS = {
    "gdp_pc":                 "PIB par habitant (USD)",
    "extractive_revenue_pc":  "Revenus extractifs par habitant (USD)",
    "health_expenditure_pc":  "Capacité fiscale par habitant (proxy, USD)",
    "mining_pct_gdp":         "Rentes extractives totales (% PIB)",
    "oil_rents_gdp":          "Rentes pétrolières (% PIB)",
    "gas_rents_gdp":          "Rentes gaz naturel (% PIB)",
    "mineral_rents_gdp":      "Rentes minières (% PIB)",
    "tax_gdp":                "Pression fiscale (% PIB)",
    "inflation":              "Inflation (%)",
}
# Keep only columns that exist
VAR_OPTIONS = {k: v for k, v in VAR_OPTIONS.items() if k in df.columns}

def var_label(col):
    return VAR_OPTIONS.get(col, col)

def dark_fig(fig, height=350):
    fig.update_layout(
        plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)",
        font=dict(color=COLORS["text"], family="Segoe UI"),
        xaxis=dict(gridcolor=COLORS["border"], zerolinecolor=COLORS["border"]),
        yaxis=dict(gridcolor=COLORS["border"], zerolinecolor=COLORS["border"]),
        legend=dict(bgcolor="rgba(0,0,0,0)", bordercolor=COLORS["border"]),
        margin=dict(l=10, r=10, t=40, b=10), height=height,
    )
    return fig

# ─── Layout ──────────────────────────────────────────────────────────
CARD = {"background": COLORS["card_bg"], "border": f"1px solid {COLORS['border']}",
        "borderRadius": "10px", "padding": "0", "marginBottom": "20px"}

app.layout = html.Div(style={"background": COLORS["dark_bg"], "minHeight": "100vh",
                               "fontFamily": "Segoe UI, sans-serif"}, children=[
    # Nav
    html.Div(style={"background": "#0a1520", "borderBottom": f"1px solid {COLORS['border']}",
                    "padding": "12px 24px", "display": "flex", "justifyContent": "space-between",
                    "alignItems": "center"}, children=[
        html.Div([
            html.Span("📐 ", style={"fontSize": "1.3rem"}),
            html.Span("Laboratoire Économétrique Africain",
                      style={"color": COLORS["text"], "fontWeight": "900", "fontSize": "1.05rem"}),
            html.Span(" · N'DRI Research Lab",
                      style={"color": COLORS["muted"], "fontSize": "0.82rem", "marginLeft": "8px"}),
        ]),
        html.Div([
            html.A("🌐 Hub", href="../index.html",
                   style={"color": "#0e9aa7", "marginRight": "14px", "textDecoration": "none"}),
            html.A("⚖️ Fiscal Obs.", href="http://localhost:8056", target="_blank",
                   style={"color": COLORS["muted"], "marginRight": "14px", "textDecoration": "none"}),
            html.A("📈 Forecasting", href="http://localhost:8501", target="_blank",
                   style={"color": COLORS["muted"], "textDecoration": "none"}),
        ]),
    ]),

    dbc.Container(fluid=True, style={"padding": "20px 24px"}, children=[
        dbc.Row([
            # Sidebar
            dbc.Col([
                html.Div(style=CARD, children=[
                    html.Div("⚙️ Configuration de l'Analyse",
                             style={"background": f"linear-gradient(135deg,{COLORS['teal']}22,{COLORS['blue']}11)",
                                    "padding": "12px 16px", "color": COLORS["teal"], "fontWeight": "700",
                                    "borderBottom": f"1px solid {COLORS['border']}", "borderRadius": "10px 10px 0 0"}),
                    html.Div(style={"padding": "16px"}, children=[
                        html.Label("Modèle économétrique", style={"color": COLORS["muted"], "fontSize": "0.75rem", "letterSpacing": "1px"}),
                        dcc.Dropdown(id="model-type", value="ols", className="mb-3", options=[
                            {"label": "MCO (OLS) — Régression linéaire",  "value": "ols"},
                            {"label": "Log-Log — Élasticités",            "value": "loglog"},
                            {"label": "ARDL(1,1) — Court/Long terme",     "value": "ardl"},
                            {"label": "VAR(1) — Multi-vectoriel",         "value": "var"},
                            {"label": "CUSUM — Rupture structurelle",     "value": "cusum"},
                        ]),
                        html.Label("Variable dépendante (Y)", style={"color": COLORS["muted"], "fontSize": "0.75rem", "letterSpacing": "1px"}),
                        dcc.Dropdown(id="y-var", value=list(VAR_OPTIONS.keys())[0], className="mb-3",
                                     options=[{"label": v, "value": k} for k, v in VAR_OPTIONS.items()]),
                        html.Label("Variable explicative (X)", style={"color": COLORS["muted"], "fontSize": "0.75rem", "letterSpacing": "1px"}),
                        dcc.Dropdown(id="x-var", value=list(VAR_OPTIONS.keys())[1] if len(VAR_OPTIONS) > 1 else list(VAR_OPTIONS.keys())[0],
                                     className="mb-3",
                                     options=[{"label": v, "value": k} for k, v in VAR_OPTIONS.items()]),
                        html.Label("Filtrer par pays", style={"color": COLORS["muted"], "fontSize": "0.75rem", "letterSpacing": "1px"}),
                        dcc.Dropdown(id="country-filter", value="all", className="mb-3",
                                     options=[{"label": "Tous les pays", "value": "all"}] +
                                             [{"label": c, "value": c} for c in sorted(df['country'].unique())]),
                        html.Hr(style={"borderColor": COLORS["border"]}),
                        dbc.Button("▶️ Lancer l'analyse", id="run-reg", color="info",
                                   className="w-100", style={"fontWeight": "700"}),
                    ]),
                ]),
                # Note méthodologique
                html.Div(style=CARD, children=[
                    html.Div("📚 Note Méthodologique",
                             style={"padding": "10px 14px", "color": COLORS["muted"], "fontWeight": "700",
                                    "fontSize": "0.8rem", "borderBottom": f"1px solid {COLORS['border']}"}),
                    html.Div(style={"padding": "14px", "fontSize": "0.8rem", "color": COLORS["muted"]}, children=[
                        html.P("Les modèles utilisent des données WDI (World Bank). La causalité de Granger teste si X "
                               "aide à prédire Y — elle ne prouve pas une causalité économique directe."),
                        html.P("Un R² élevé avec des variables non stationnaires peut indiquer une corrélation spurieuse. "
                               "Vérifiez la stationnarité avant d'interpréter."),
                    ]),
                ]),
            ], md=4),

            # Results panel
            dbc.Col([
                dbc.Tabs(id="tabs-analysis", active_tab="tab-corr", children=[
                    dbc.Tab(label="📊 Corrélation", tab_id="tab-corr"),
                    dbc.Tab(label="📐 Régression", tab_id="tab-stats"),
                    dbc.Tab(label="⚡ Granger",     tab_id="tab-causality"),
                    dbc.Tab(label="🔍 Stationnarité", tab_id="tab-adf"),
                    dbc.Tab(label="🗺️ Panel complet", tab_id="tab-panel"),
                ]),
                html.Div(id="tab-content", style={"background": COLORS["card_bg"],
                                                   "border": f"1px solid {COLORS['border']}",
                                                   "borderTop": "none", "borderRadius": "0 0 10px 10px",
                                                   "padding": "20px", "minHeight": "400px"}),
            ], md=8),
        ]),
    ]),

    # Footer
    html.Div(style={"borderTop": f"1px solid {COLORS['border']}", "padding": "10px 24px",
                    "color": COLORS["muted"], "fontSize": "0.72rem", "textAlign": "center"}, children=[
        f"N'DRI Research Lab · Anthelme Affounou N'DRI · Port 8055 · {len(df)} observations · {df['country'].nunique()} pays"
    ]),
])


# ─── Main callback ────────────────────────────────────────────────────
@app.callback(
    Output("tab-content", "children"),
    Input("tabs-analysis", "active_tab"),
    Input("run-reg", "n_clicks"),
    State("model-type", "value"),
    State("x-var", "value"),
    State("y-var", "value"),
    State("country-filter", "value"),
    prevent_initial_call=False,
)
def render(active_tab, n_clicks, model_type, x_var, y_var, country_val):
    dff = df.copy()
    if country_val != "all":
        dff = dff[dff["country"] == country_val]

    y_lbl = var_label(y_var)
    x_lbl = var_label(x_var)

    def insufficient():
        return dbc.Alert(f"Données insuffisantes pour {country_val}. Essayez 'Tous les pays'.", color="warning")

    # ── Tab: Corrélation ─────────────────────────────────────────────
    if active_tab == "tab-corr":
        data = dff[[x_var, y_var]].dropna()
        if len(data) < 3:
            return insufficient()
        fig = dark_fig(px.scatter(
            dff, x=x_var, y=y_var,
            color="country" if country_val == "all" else None,
            trendline="ols", trendline_scope="overall" if country_val == "all" else "trace",
            labels={x_var: x_lbl, y_var: y_lbl},
            title=f"Relation : {x_lbl} → {y_lbl}",
            color_discrete_sequence=px.colors.qualitative.Set2,
        ))
        r = data.corr().iloc[0, 1]
        return html.Div([
            dcc.Graph(figure=fig),
            dbc.Alert([
                html.Strong("Corrélation de Pearson : "),
                f"r = {r:.3f} — {'forte' if abs(r) > 0.7 else 'modérée' if abs(r) > 0.4 else 'faible'} corrélation {'positive' if r > 0 else 'négative'}. ",
                html.Br(),
                html.Em("Rappel : corrélation ≠ causalité. Utilisez l'onglet ⚡ Granger pour tester la causalité."),
            ], color="info"),
        ])

    # ── Tab: Régression ──────────────────────────────────────────────
    elif active_tab == "tab-stats":
        model_df = dff[[x_var, y_var]].dropna()
        if len(model_df) < 6:
            return insufficient()

        try:
            if model_type == "loglog":
                pos = model_df[(model_df[x_var] > 0) & (model_df[y_var] > 0)]
                if len(pos) < 4:
                    return dbc.Alert("Valeurs positives insuffisantes pour log-log.", color="warning")
                y_s = np.log(pos[y_var]); X_s = sm.add_constant(np.log(pos[x_var]))
                title = f"Régression Log-Log — Élasticité"
            elif model_type == "ardl":
                model_df = model_df.copy()
                model_df['y_l1'] = model_df[y_var].shift(1)
                model_df['x_l1'] = model_df[x_var].shift(1)
                model_df = model_df.dropna()
                y_s = model_df[y_var]; X_s = sm.add_constant(model_df[[x_var, 'y_l1', 'x_l1']])
                title = "ARDL(1,1) — Court & Long terme"
            elif model_type == "var":
                var_data = model_df[[y_var, x_var]].dropna()
                if len(var_data) < 12:
                    return dbc.Alert("Minimum 12 observations pour VAR.", color="warning")
                res_var = VAR(var_data).fit(1)
                return html.Div([
                    html.H5("Modèle VAR(1)", style={"color": COLORS["teal"]}),
                    html.Pre(res_var.summary().as_text(),
                             style={"background": "#0a1520", "color": COLORS["text"], "padding": "14px",
                                    "border": f"1px solid {COLORS['border']}", "borderRadius": "6px",
                                    "fontSize": "0.75rem", "overflowX": "auto"}),
                    dbc.Alert("Le modèle VAR capture les interactions bidirectionnelles. "
                              "Comparez avec le test de Granger pour identifier la direction de causalité.", color="info"),
                ])
            elif model_type == "cusum":
                y_s = model_df[y_var]; X_s = sm.add_constant(model_df[x_var])
                res_ols = sm.OLS(y_s, X_s)
                rols = recursive_olsresiduals(res_ols)
                cusum = rols[5]
                breaks = np.where(np.abs(cusum) > 1)[0]
                fig_c = go.Figure()
                fig_c.add_trace(go.Scatter(y=cusum, name='CUSUM', line=dict(color=COLORS["teal"])))
                fig_c.add_hline(y=1, line_dash="dash", line_color="#e74c3c", annotation_text="+1 (critique)")
                fig_c.add_hline(y=-1, line_dash="dash", line_color="#e74c3c", annotation_text="-1 (critique)")
                dark_fig(fig_c)
                conclusion = f"⚠️ Rupture structurelle détectée à l'obs. {breaks[0]}" if len(breaks) > 0 else "✅ Paramètres stables sur toute la période"
                return html.Div([
                    html.H5("Test CUSUM — Stabilité des paramètres", style={"color": COLORS["teal"]}),
                    dcc.Graph(figure=fig_c),
                    dbc.Alert(conclusion, color="warning" if len(breaks) > 0 else "success"),
                ])
            else:
                y_s = model_df[y_var]; X_s = sm.add_constant(model_df[x_var])
                title = "Régression OLS (MCO)"

            model = sm.OLS(y_s, X_s).fit()

            # Results table
            params_df = pd.DataFrame({
                "Variable": list(model.params.index),
                "Coefficient": [f"{v:.4f}" for v in model.params.values],
                "Std. Erreur": [f"{v:.4f}" for v in model.bse.values],
                "t-stat": [f"{v:.3f}" for v in model.tvalues],
                "P-value": [f"{v:.4f}" for v in model.pvalues.values],
                "Sig.": ["***" if p < 0.01 else "**" if p < 0.05 else "*" if p < 0.1 else ""
                         for p in model.pvalues.values],
            })

            # Fitted vs actual plot
            fig_fit = go.Figure()
            fig_fit.add_trace(go.Scatter(y=list(y_s), name='Observé', line=dict(color='#c8d8ea', width=1.5)))
            fig_fit.add_trace(go.Scatter(y=list(model.fittedvalues), name='Ajusté',
                                          line=dict(color=COLORS["teal"], dash='dash', width=2)))
            dark_fig(fig_fit, height=280)
            fig_fit.update_layout(title="Valeurs observées vs ajustées")

            # Insight text
            sig_global = model.f_pvalue < 0.05
            insight_color = "success" if sig_global else "warning"
            insight_text = [
                html.Strong("IA Insight : "),
                f"R² = {model.rsquared:.4f} → le modèle explique {model.rsquared*100:.1f}% de la variance de {y_lbl}. ",
                f"Le test F est {'significatif' if sig_global else 'non significatif'} (F={model.fvalue:.2f}, p={model.f_pvalue:.4f}). ",
            ]
            if model_type == "loglog":
                coef = list(model.params.values)[1]
                insight_text.append(f"Élasticité = {coef:.3f} : une hausse de 1% de {x_lbl} est associée à une {'hausse' if coef > 0 else 'baisse'} de {abs(coef):.2f}% de {y_lbl}.")

            return html.Div([
                dbc.Row([
                    dbc.Col(html.Div([
                        html.Div("R²", style={"color": COLORS["muted"], "fontSize": "0.7rem"}),
                        html.Div(f"{model.rsquared:.4f}", style={"color": COLORS["teal"], "fontSize": "1.6rem", "fontWeight": "900"}),
                    ], style={"background": f"{COLORS['teal']}11", "border": f"1px solid {COLORS['teal']}33",
                               "borderRadius": "6px", "padding": "10px", "textAlign": "center"}), md=3),
                    dbc.Col(html.Div([
                        html.Div("R² ajusté", style={"color": COLORS["muted"], "fontSize": "0.7rem"}),
                        html.Div(f"{model.rsquared_adj:.4f}", style={"color": COLORS["blue"], "fontSize": "1.6rem", "fontWeight": "900"}),
                    ], style={"background": f"{COLORS['blue']}11", "border": f"1px solid {COLORS['blue']}33",
                               "borderRadius": "6px", "padding": "10px", "textAlign": "center"}), md=3),
                    dbc.Col(html.Div([
                        html.Div("AIC", style={"color": COLORS["muted"], "fontSize": "0.7rem"}),
                        html.Div(f"{model.aic:.1f}", style={"color": COLORS["gold"], "fontSize": "1.6rem", "fontWeight": "900"}),
                    ], style={"background": f"{COLORS['gold']}11", "border": f"1px solid {COLORS['gold']}33",
                               "borderRadius": "6px", "padding": "10px", "textAlign": "center"}), md=3),
                    dbc.Col(html.Div([
                        html.Div("N obs.", style={"color": COLORS["muted"], "fontSize": "0.7rem"}),
                        html.Div(f"{int(model.nobs)}", style={"color": "#2ca05a", "fontSize": "1.6rem", "fontWeight": "900"}),
                    ], style={"background": "#2ca05a11", "border": "1px solid #2ca05a33",
                               "borderRadius": "6px", "padding": "10px", "textAlign": "center"}), md=3),
                ], className="mb-3"),

                html.H6(title, style={"color": COLORS["teal"], "marginBottom": "10px"}),
                dbc.Table.from_dataframe(params_df, striped=False, bordered=False,
                                          dark=True, hover=True, size="sm"),
                dcc.Graph(figure=fig_fit),
                dbc.Alert(insight_text, color=insight_color),
            ])
        except Exception as e:
            return dbc.Alert(f"Erreur lors de l'estimation : {str(e)}", color="danger")

    # ── Tab: Granger ─────────────────────────────────────────────────
    elif active_tab == "tab-causality":
        if country_val == "all":
            return dbc.Alert(
                "Sélectionnez un pays spécifique pour tester la causalité de Granger (séries temporelles).",
                color="info"
            )
        data_causal = dff[[y_var, x_var]].dropna().sort_values(by=dff.columns[0] if 'year' in dff.columns else dff.columns[0])
        if 'year' in dff.columns:
            data_causal = dff.sort_values('year')[[y_var, x_var]].dropna()

        if len(data_causal) < 8:
            return dbc.Alert(
                f"Pas assez de données temporelles pour {country_val} ({len(data_causal)} obs. disponibles, minimum 8).",
                color="danger"
            )
        try:
            # Run Granger in both directions
            results_xy = grangercausalitytests(data_causal[[y_var, x_var]], maxlag=min(4, len(data_causal)//3), verbose=False)
            results_yx = grangercausalitytests(data_causal[[x_var, y_var]], maxlag=min(4, len(data_causal)//3), verbose=False)

            def granger_rows(results, direction):
                rows = []
                for lag, res in results.items():
                    f = res[0]['ssr_ftest']
                    rows.append({
                        "Direction": direction,
                        "Lag": lag,
                        "F-stat.": round(f[0], 3),
                        "P-value": round(f[1], 4),
                        "Résultat": "✅ Significatif" if f[1] < 0.05 else "❌ Non significatif",
                    })
                return rows

            dir_xy = f"X → Y : {x_lbl[:25]} → {y_lbl[:25]}"
            dir_yx = f"Y → X : {y_lbl[:25]} → {x_lbl[:25]}"
            all_rows = granger_rows(results_xy, dir_xy) + granger_rows(results_yx, dir_yx)
            res_df = pd.DataFrame(all_rows)

            # Determine causality structure
            sig_xy = any(r['Résultat'].startswith("✅") for r in granger_rows(results_xy, ""))
            sig_yx = any(r['Résultat'].startswith("✅") for r in granger_rows(results_yx, ""))

            if sig_xy and sig_yx:
                conclusion = f"🔄 Causalité bilatérale entre {x_lbl} et {y_lbl}"
                color = "info"
            elif sig_xy:
                conclusion = f"➡️ Causalité unilatérale : {x_lbl} cause {y_lbl}"
                color = "success"
            elif sig_yx:
                conclusion = f"⬅️ Causalité unilatérale : {y_lbl} cause {x_lbl}"
                color = "warning"
            else:
                conclusion = f"❌ Aucune causalité de Granger détectée entre ces deux variables"
                color = "secondary"

            # Bar chart of p-values
            fig_g = go.Figure()
            for direction, results in [(dir_xy, results_xy), (dir_yx, results_yx)]:
                lags = list(results.keys())
                pvals = [results[l][0]['ssr_ftest'][1] for l in lags]
                fig_g.add_trace(go.Bar(name=direction[:35], x=[f"Lag {l}" for l in lags], y=pvals,
                                        marker_color=[COLORS["teal"] if p < 0.05 else "#e74c3c" for p in pvals]))
            fig_g.add_hline(y=0.05, line_dash="dash", line_color=COLORS["gold"],
                             annotation_text="Seuil 5%", annotation_position="right")
            dark_fig(fig_g, height=300)
            fig_g.update_layout(barmode='group', title=f"P-values du test de Granger ({country_val})",
                                  yaxis_title="P-value", legend=dict(orientation='h', y=-0.25))

            return html.Div([
                html.H5(f"Test de Causalité de Granger · {country_val}", style={"color": COLORS["teal"]}),
                html.P(f"H₀ : X ne cause pas Y. Si p < 0.05, on rejette H₀.",
                       style={"color": COLORS["muted"], "fontSize": "0.85rem"}),
                dbc.Table.from_dataframe(res_df, dark=True, striped=False, hover=True, size="sm"),
                dcc.Graph(figure=fig_g),
                dbc.Alert([html.Strong(conclusion), html.Br(),
                           html.Em("Note : Causalité de Granger ≠ causalité économique. "
                                   "Elle indique seulement si X aide à prédire Y.")],
                          color=color),
            ])
        except Exception as e:
            return dbc.Alert(f"Erreur test de Granger : {str(e)}", color="danger")

    # ── Tab: ADF Stationarity ────────────────────────────────────────
    elif active_tab == "tab-adf":
        results_adf = []
        for col in [y_var, x_var]:
            series = dff[col].dropna()
            if len(series) < 5:
                results_adf.append({"Variable": var_label(col), "ADF Stat.": "—", "P-value": "—",
                                     "Stationnaire": "⚠️ Insuffisant"})
                continue
            try:
                r = adfuller(series, autolag='AIC')
                is_stat = r[1] < 0.05
                results_adf.append({
                    "Variable": var_label(col),
                    "ADF Stat.": round(r[0], 4),
                    "P-value": round(r[1], 4),
                    "CV 5%": round(r[4]['5%'], 4),
                    "Lags": r[2],
                    "N obs.": r[3],
                    "Stationnaire": "✅ I(0)" if is_stat else "⚠️ Non stationnaire",
                })
            except Exception as e:
                results_adf.append({"Variable": var_label(col), "Stationnaire": f"Erreur: {e}"})

        adf_df = pd.DataFrame(results_adf)
        return html.Div([
            html.H5("Test ADF — Racine Unitaire (Augmented Dickey-Fuller)", style={"color": COLORS["teal"]}),
            html.P("H₀ : La série possède une racine unitaire (non stationnaire). Si p < 0.05, on rejette H₀.",
                   style={"color": COLORS["muted"], "fontSize": "0.85rem"}),
            dbc.Table.from_dataframe(adf_df, dark=True, striped=False, hover=True, size="sm"),
            dbc.Alert([
                html.Strong("Interprétation : "),
                "Si les séries sont non stationnaires, différenciez-les avant OLS ou ARDL pour éviter les régressions spurieuses. "
                "Pour les séries coïntégrées, le modèle à correction d'erreur (ECM) est recommandé."
            ], color="info"),
        ])

    # ── Tab: Panel ───────────────────────────────────────────────────
    elif active_tab == "tab-panel":
        num_cols = [c for c in VAR_OPTIONS.keys() if c in dff.columns]
        if len(num_cols) < 2:
            return dbc.Alert("Données insuffisantes.", color="warning")

        panel_data = dff[['country', 'year'] + num_cols].copy() if 'year' in dff.columns else dff[['country'] + num_cols].copy()
        corr = dff[num_cols].corr().round(3)

        fig_h = px.imshow(corr, color_continuous_scale='RdBu_r', zmin=-1, zmax=1,
                           text_auto='.2f', aspect='auto', title="Matrice de corrélations — Panel africain")
        dark_fig(fig_h, height=380)
        fig_h.update_coloraxes(colorbar=dict(tickfont=dict(color=COLORS["text"])))

        # Summary by country
        if 'country' in dff.columns:
            summary = dff.groupby('country')[num_cols].mean().round(2).reset_index()
            summary.columns = ['Pays'] + [VAR_OPTIONS.get(c, c) for c in num_cols]
        else:
            summary = pd.DataFrame()

        return html.Div([
            html.H5("Vue Panel — Corrélations & Statistiques", style={"color": COLORS["teal"]}),
            dcc.Graph(figure=fig_h),
            html.H6("Moyennes par pays", style={"color": COLORS["muted"], "marginTop": "16px"}),
            dbc.Table.from_dataframe(summary if not summary.empty else pd.DataFrame(),
                                      dark=True, striped=False, hover=True, size="sm", responsive=True),
        ])

    return html.Div("Sélectionnez un onglet.", style={"color": COLORS["muted"]})


if __name__ == "__main__":
    app.run_server(debug=False, port=8055)
