"""
Random Forest — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _ensemble_concept():
    """Show how multiple weak learners combine into a strong one."""
    np.random.seed(42)
    n_trees = list(range(1, 51))
    single_acc = 0.72
    # Ensemble accuracy increases with more trees, then plateaus
    ensemble_acc = [single_acc + (0.96 - single_acc) * (1 - np.exp(-0.12 * t)) + np.random.normal(0, 0.005) for t in n_trees]

    data = [
        {
            "x": n_trees, "y": ensemble_acc,
            "mode": "lines+markers", "type": "scatter", "name": "Random Forest Accuracy",
            "line": {"color": "#34d399", "width": 3},
            "marker": {"size": 4},
        },
        {
            "x": [1, 50], "y": [single_acc, single_acc],
            "mode": "lines", "type": "scatter", "name": f"Single Tree ({single_acc:.0%})",
            "line": {"color": "#FF6584", "width": 2, "dash": "dash"},
        },
    ]
    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Accuracy vs Number of Trees", "font": {"size": 18}},
        "xaxis": {"title": "Number of Trees", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Accuracy", "gridcolor": "rgba(255,255,255,0.08)", "range": [0.65, 1.0]},
        "legend": {"x": 0.55, "y": 0.2},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _feature_importance():
    """Generate a feature importance bar chart."""
    features = ["Petal Length", "Petal Width", "Sepal Length", "Sepal Width"]
    importance = [0.42, 0.38, 0.12, 0.08]
    colors = ["#6C63FF", "#a78bfa", "#FF6584", "#fbbf24"]

    data = [{
        "y": features[::-1], "x": importance[::-1],
        "type": "bar", "orientation": "h",
        "marker": {"color": colors[::-1], "line": {"width": 1.5, "color": "#fff"}},
        "text": [f"{v:.0%}" for v in importance[::-1]],
        "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": "Feature Importance (Iris Dataset)", "font": {"size": 18}},
        "xaxis": {"title": "Importance", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"gridcolor": "rgba(255,255,255,0.08)"},
        "margin": {"l": 120, "r": 50, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _bagging_visualization():
    """Show bootstrap sampling concept."""
    np.random.seed(42)
    original = np.arange(1, 11)
    samples = [np.random.choice(original, size=10, replace=True).tolist() for _ in range(5)]

    data = []
    colors = ["#6C63FF", "#FF6584", "#34d399", "#fbbf24", "#a78bfa"]
    for i, s in enumerate(samples):
        counts = [s.count(v) for v in range(1, 11)]
        data.append({
            "x": list(range(1, 11)), "y": counts,
            "name": f"Bootstrap Sample {i+1}",
            "type": "bar",
            "marker": {"color": colors[i], "opacity": 0.7},
        })

    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Bootstrap Sampling (5 Samples from 10 Points)", "font": {"size": 18}},
        "xaxis": {"title": "Data Point Index", "gridcolor": "rgba(255,255,255,0.08)", "dtick": 1},
        "yaxis": {"title": "Times Selected", "gridcolor": "rgba(255,255,255,0.08)", "dtick": 1},
        "barmode": "group",
        "legend": {"x": 0.7, "y": 0.98},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def get_content() -> dict:
    return {
        "title": "Random Forest",
        "icon": "🌲",
        "subtitle": "Ensemble of decision trees for robust predictions",
        "sections": [
            {
                "id": "introduction",
                "heading": "What is Random Forest?",
                "type": "text",
                "content": (
                    "Random Forest is an <strong>ensemble learning</strong> method that builds multiple decision trees "
                    "and combines their predictions through <strong>majority voting</strong> (classification) or "
                    "<strong>averaging</strong> (regression).\n\n"
                    "By introducing randomness in two ways — <strong>bootstrap sampling</strong> (bagging) and "
                    "<strong>random feature selection</strong> — it creates diverse trees that, when combined, "
                    "are far more accurate and robust than any single tree."
                ),
            },
            {
                "id": "ensemble",
                "heading": "Power of the Ensemble",
                "type": "graph",
                "description": (
                    "A single decision tree might be only ~72% accurate, but combining 20–50 trees into a Random Forest "
                    "pushes accuracy above 95%. More trees help up to a point, then improvements plateau."
                ),
                "graph_json": _ensemble_concept(),
            },
            {
                "id": "bagging",
                "heading": "Bootstrap Sampling (Bagging)",
                "type": "graph",
                "description": (
                    "Each tree is trained on a <strong>bootstrap sample</strong> — a random sample with replacement "
                    "from the training data. Some points appear multiple times, others are left out (called "
                    "<em>out-of-bag</em> samples, used for validation)."
                ),
                "graph_json": _bagging_visualization(),
            },
            {
                "id": "how-it-works",
                "heading": "How Random Forest Works",
                "type": "list",
                "entries": [
                    {"title": "1. Bootstrap Sample", "detail": "For each tree, randomly sample N data points with replacement from the training set."},
                    {"title": "2. Random Feature Selection", "detail": "At each split, consider only a random subset of √p features (classification) or p/3 (regression), not all features."},
                    {"title": "3. Grow Individual Trees", "detail": "Each tree grows deep (usually no max_depth) to minimize bias. The ensemble handles variance."},
                    {"title": "4. Aggregate Predictions", "detail": "For classification: majority vote. For regression: average of all tree predictions."},
                ],
            },
            {
                "id": "feature-importance",
                "heading": "Feature Importance",
                "type": "graph",
                "description": (
                    "Random Forest naturally ranks features by importance — measured by how much each feature "
                    "reduces impurity across all trees. This is invaluable for feature selection and understanding your data."
                ),
                "graph_json": _feature_importance(),
            },
            {
                "id": "math",
                "heading": "Key Concepts",
                "type": "math",
                "content": [
                    {
                        "label": "Ensemble Prediction (Classification)",
                        "formula": "\\hat{y} = \\text{mode}\\{h_1(x), h_2(x), \\ldots, h_T(x)\\}",
                        "explanation": "Final prediction is the majority vote across T individual trees."
                    },
                    {
                        "label": "Ensemble Prediction (Regression)",
                        "formula": "\\hat{y} = \\frac{1}{T} \\sum_{t=1}^{T} h_t(x)",
                        "explanation": "Final prediction is the average of all tree predictions."
                    },
                    {
                        "label": "Out-of-Bag Error",
                        "formula": "\\text{OOB Error} = \\frac{1}{n} \\sum_{i=1}^{n} \\mathbb{1}(y_i \\neq \\hat{y}_i^{OOB})",
                        "explanation": "Free validation metric — each point is predicted only by trees that didn't see it during training."
                    },
                ],
            },
            {
                "id": "implementation",
                "heading": "Python Implementation",
                "type": "code",
                "language": "python",
                "content": (
                    "from sklearn.ensemble import RandomForestClassifier\n"
                    "from sklearn.model_selection import train_test_split\n"
                    "from sklearn.datasets import load_iris\n"
                    "from sklearn.metrics import accuracy_score\n"
                    "\n"
                    "# Load data\n"
                    "iris = load_iris()\n"
                    "X, y = iris.data, iris.target\n"
                    "\n"
                    "# Train-test split\n"
                    "X_train, X_test, y_train, y_test = train_test_split(\n"
                    "    X, y, test_size=0.2, random_state=42\n"
                    ")\n"
                    "\n"
                    "# Fit Random Forest\n"
                    "model = RandomForestClassifier(\n"
                    "    n_estimators=100,    # number of trees\n"
                    "    max_features='sqrt', # random feature subset\n"
                    "    oob_score=True,      # enable OOB error\n"
                    "    random_state=42\n"
                    ")\n"
                    "model.fit(X_train, y_train)\n"
                    "\n"
                    "# Evaluate\n"
                    "print(f'Test Accuracy: {accuracy_score(y_test, model.predict(X_test)):.4f}')\n"
                    "print(f'OOB Score:     {model.oob_score_:.4f}')\n"
                    "\n"
                    "# Feature importance\n"
                    "for name, imp in zip(iris.feature_names, model.feature_importances_):\n"
                    "    print(f'  {name}: {imp:.3f}')\n"
                ),
            },
            {
                "id": "tips",
                "heading": "Hyperparameter Tips",
                "type": "list",
                "entries": [
                    {"title": "n_estimators", "detail": "More trees = more stable, but slower. 100–500 is usually enough. Monitor OOB error to decide."},
                    {"title": "max_features", "detail": "Controls randomness. 'sqrt' (classification) and 'log2' are common defaults. Lower = more diverse trees."},
                    {"title": "max_depth", "detail": "Usually left unlimited (each tree grows fully). If overfitting persists, reduce it."},
                    {"title": "min_samples_leaf", "detail": "Prevents tiny leaves. Setting to 3–5 can reduce overfitting without hurting much."},
                    {"title": "Random Forest vs. Gradient Boosting", "detail": "RF is easier to tune and parallelize. Gradient Boosting often has higher accuracy but is more sensitive to hyperparameters."},
                ],
            },
        ],
    }
