"""
Gradient Boosting — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _boosting_stages():
    """Show how boosting combines weak learners stage by stage."""
    np.random.seed(42)
    stages = list(range(1, 31))
    train_loss = [1.0 * np.exp(-0.15 * s) + 0.02 for s in stages]
    val_loss = [1.0 * np.exp(-0.12 * s) + 0.08 + 0.005 * max(0, s - 15) for s in stages]

    data = [
        {"x": stages, "y": train_loss, "mode": "lines", "type": "scatter", "name": "Training Loss",
         "line": {"color": "#34d399", "width": 3}},
        {"x": stages, "y": val_loss, "mode": "lines", "type": "scatter", "name": "Validation Loss",
         "line": {"color": "#FF6584", "width": 3}},
        {"x": [15, 15], "y": [0, 1.1], "mode": "lines", "type": "scatter", "name": "Optimal Stages",
         "line": {"color": "#fbbf24", "width": 2, "dash": "dash"}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Loss vs Number of Boosting Stages", "font": {"size": 18}},
        "xaxis": {"title": "Number of Weak Learners", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Loss", "gridcolor": "rgba(255,255,255,0.08)"},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _learning_rate_effect():
    """Show effect of learning rate on convergence."""
    stages = list(range(1, 101))
    lr_high = [1.0 * np.exp(-0.1 * s) + 0.15 + 0.08 * np.sin(s * 0.3) for s in stages]
    lr_med = [1.0 * np.exp(-0.05 * s) + 0.06 for s in stages]
    lr_low = [1.0 * np.exp(-0.015 * s) + 0.04 for s in stages]

    data = [
        {"x": stages, "y": lr_high, "mode": "lines", "type": "scatter", "name": "lr = 0.5 (too high)",
         "line": {"color": "#FF6584", "width": 2.5}},
        {"x": stages, "y": lr_med, "mode": "lines", "type": "scatter", "name": "lr = 0.1 (good)",
         "line": {"color": "#34d399", "width": 3}},
        {"x": stages, "y": lr_low, "mode": "lines", "type": "scatter", "name": "lr = 0.01 (slow)",
         "line": {"color": "#6C63FF", "width": 2.5}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Effect of Learning Rate", "font": {"size": 18}},
        "xaxis": {"title": "Boosting Stages", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Validation Loss", "gridcolor": "rgba(255,255,255,0.08)"},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _algorithm_comparison():
    """Compare popular gradient boosting implementations."""
    algos = ["XGBoost", "LightGBM", "CatBoost", "sklearn GBM"]
    accuracy = [0.962, 0.958, 0.955, 0.948]
    train_time = [12.3, 4.1, 8.7, 24.5]
    colors = ["#6C63FF", "#34d399", "#FF6584", "#a78bfa"]

    data = [
        {"x": algos, "y": accuracy, "name": "Accuracy", "type": "bar",
         "marker": {"color": colors, "line": {"width": 1.5, "color": "#fff"}},
         "text": [f"{a:.1%}" for a in accuracy], "textposition": "outside",
         "textfont": {"color": "#e0e0e0", "size": 13}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Gradient Boosting Implementations Compared", "font": {"size": 18}},
        "yaxis": {"title": "Accuracy", "gridcolor": "rgba(255,255,255,0.08)", "range": [0.93, 0.98]},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def get_content() -> dict:
    return {
        "title": "Gradient Boosting",
        "icon": "🚀",
        "subtitle": "Sequentially building stronger models from weak learners",
        "sections": [
            {"id": "introduction", "heading": "What is Gradient Boosting?", "type": "text",
             "content": ("Gradient Boosting is an ensemble technique that builds models <strong>sequentially</strong>. "
                         "Each new model corrects the <strong>residual errors</strong> of the previous ensemble.\n\n"
                         "Unlike Random Forest (which trains trees in parallel), boosting is <strong>additive</strong> — "
                         "each tree focuses on what the ensemble still gets wrong. It's consistently one of the "
                         "top-performing algorithms in ML competitions.")},
            {"id": "how-it-works", "heading": "How It Works", "type": "list",
             "entries": [
                 {"title": "1. Start with a Simple Prediction", "detail": "Initialize with a constant (e.g., the mean for regression, log-odds for classification)."},
                 {"title": "2. Compute Residuals", "detail": "Calculate the errors (residuals) of the current ensemble's predictions."},
                 {"title": "3. Fit a Weak Learner", "detail": "Train a shallow decision tree (depth 3-8) to predict the residuals, not the original target."},
                 {"title": "4. Update the Ensemble", "detail": "Add the new tree's predictions (scaled by learning rate) to the ensemble: F(x) = F(x) + lr × h(x)."},
                 {"title": "5. Repeat", "detail": "Continue adding trees until validation loss stops improving (early stopping)."},
             ]},
            {"id": "stages", "heading": "Boosting Convergence", "type": "graph",
             "description": "Training loss keeps decreasing, but validation loss eventually increases — a sign of overfitting. The yellow line marks where to stop (early stopping).",
             "graph_json": _boosting_stages()},
            {"id": "math", "heading": "The Mathematics", "type": "math",
             "content": [
                 {"label": "Additive Model", "formula": "F_M(x) = F_0(x) + \\sum_{m=1}^{M} \\eta \\cdot h_m(x)",
                  "explanation": "The final model is the sum of M weak learners (h), each scaled by learning rate η."},
                 {"label": "Gradient Step", "formula": "h_m(x) = \\arg\\min_h \\sum_{i=1}^{n} L(y_i, F_{m-1}(x_i) + h(x_i))",
                  "explanation": "Each new tree minimizes the loss function by fitting the negative gradient (pseudo-residuals)."},
                 {"label": "Pseudo-Residuals", "formula": "r_{im} = -\\frac{\\partial L(y_i, F(x_i))}{\\partial F(x_i)}",
                  "explanation": "The 'gradient' in gradient boosting — each tree fits the direction of steepest descent of the loss function."},
             ]},
            {"id": "learning-rate", "heading": "Learning Rate Effect", "type": "graph",
             "description": "Lower learning rates need more trees but generalize better. Too high = oscillation. The sweet spot is typically 0.01–0.1 with early stopping.",
             "graph_json": _learning_rate_effect()},
            {"id": "comparison", "heading": "Popular Implementations", "type": "graph",
             "description": "XGBoost, LightGBM, and CatBoost are optimized versions with different strengths. LightGBM is fastest, XGBoost is most popular, CatBoost handles categoricals natively.",
             "graph_json": _algorithm_comparison()},
            {"id": "implementation", "heading": "Python Implementation", "type": "code", "language": "python",
             "content": ("from sklearn.ensemble import GradientBoostingClassifier\nfrom sklearn.model_selection import train_test_split\n"
                         "from sklearn.datasets import load_iris\nfrom sklearn.metrics import accuracy_score\n\n"
                         "# Load data\niris = load_iris()\nX_train, X_test, y_train, y_test = train_test_split(\n"
                         "    iris.data, iris.target, test_size=0.2, random_state=42\n)\n\n"
                         "# Gradient Boosting with early stopping\nmodel = GradientBoostingClassifier(\n"
                         "    n_estimators=200,\n    learning_rate=0.1,\n    max_depth=4,\n"
                         "    subsample=0.8,         # stochastic boosting\n"
                         "    validation_fraction=0.1,\n    n_iter_no_change=10,   # early stopping\n"
                         "    random_state=42\n)\nmodel.fit(X_train, y_train)\n\n"
                         "print(f'Trees used: {model.n_estimators_}')\n"
                         "print(f'Accuracy:   {accuracy_score(y_test, model.predict(X_test)):.4f}')\n")},
            {"id": "tips", "heading": "Hyperparameter Tips", "type": "list",
             "entries": [
                 {"title": "Use Early Stopping", "detail": "Set n_iter_no_change to stop training when validation performance plateaus. Prevents overfitting automatically."},
                 {"title": "Lower Learning Rate + More Trees", "detail": "lr=0.01 with 1000+ trees often beats lr=0.1 with 100 trees. Use early stopping to find the right count."},
                 {"title": "Subsample < 1.0", "detail": "Stochastic gradient boosting (subsample=0.8) adds regularization and speeds up training."},
                 {"title": "Use XGBoost or LightGBM", "detail": "For production, use XGBoost (pip install xgboost) or LightGBM (pip install lightgbm) — much faster than sklearn."},
                 {"title": "Feature Importance Built-in", "detail": "Like Random Forest, gradient boosting provides feature importance scores out of the box."},
             ]},
        ],
    }
