"""
Decision Trees — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _tree_structure():
    """Generate a tree-like visualization using a treemap."""
    labels = [
        "Is Feature₁ > 5?",
        "Is Feature₂ > 3?", "Class B (Leaf)",
        "Class A (Leaf)", "Class B (Leaf)"
    ]
    parents = ["", "Is Feature₁ > 5?", "Is Feature₁ > 5?",
               "Is Feature₂ > 3?", "Is Feature₂ > 3?"]
    values = [0, 0, 30, 45, 25]
    colors = ["#6C63FF", "#a78bfa", "#FF6584", "#34d399", "#FF6584"]

    data = [{
        "type": "treemap",
        "labels": labels,
        "parents": parents,
        "values": values,
        "marker": {"colors": colors, "line": {"width": 2, "color": "#1a1a2e"}},
        "textfont": {"size": 14, "color": "#fff"},
        "textinfo": "label",
    }]
    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Decision Tree Structure (Treemap)", "font": {"size": 18}},
        "margin": {"l": 10, "r": 10, "t": 50, "b": 10},
    }
    return json.dumps({"data": data, "layout": layout})


def _gini_vs_entropy():
    """Compare Gini Impurity and Entropy."""
    p = np.linspace(0.001, 0.999, 200)
    gini = 2 * p * (1 - p)
    entropy = -(p * np.log2(p) + (1 - p) * np.log2(1 - p))
    misclass = 1 - np.maximum(p, 1 - p)

    data = [
        {
            "x": p.tolist(), "y": entropy.tolist(),
            "mode": "lines", "type": "scatter", "name": "Entropy",
            "line": {"color": "#FF6584", "width": 3},
        },
        {
            "x": p.tolist(), "y": gini.tolist(),
            "mode": "lines", "type": "scatter", "name": "Gini Impurity",
            "line": {"color": "#6C63FF", "width": 3},
        },
        {
            "x": p.tolist(), "y": misclass.tolist(),
            "mode": "lines", "type": "scatter", "name": "Misclassification Error",
            "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": "Impurity Measures Comparison", "font": {"size": 18}},
        "xaxis": {"title": "Probability of Class 1", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Impurity", "gridcolor": "rgba(255,255,255,0.08)"},
        "legend": {"x": 0.3, "y": 0.02},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _decision_boundary():
    """Generate a 2D decision boundary for a decision tree."""
    np.random.seed(42)
    n = 100
    X = np.random.randn(n, 2)
    y = ((X[:, 0] > 0) & (X[:, 1] > 0)).astype(int) | ((X[:, 0] < 0) & (X[:, 1] < 0)).astype(int)

    xx, yy = np.meshgrid(np.linspace(-3, 3, 80), np.linspace(-3, 3, 80))
    zz = ((xx > 0) & (yy > 0)).astype(int) | ((xx < 0) & (yy < 0)).astype(int)

    data = [
        {
            "x": xx[0].tolist(), "y": yy[:, 0].tolist(), "z": zz.tolist(),
            "type": "heatmap",
            "colorscale": [[0, "rgba(108,99,255,0.15)"], [1, "rgba(255,101,132,0.15)"]],
            "showscale": False,
        },
        {
            "x": X[y == 0, 0].tolist(), "y": X[y == 0, 1].tolist(),
            "mode": "markers", "type": "scatter", "name": "Class 0",
            "marker": {"size": 9, "color": "#6C63FF", "line": {"width": 1, "color": "#fff"}},
        },
        {
            "x": X[y == 1, 0].tolist(), "y": X[y == 1, 1].tolist(),
            "mode": "markers", "type": "scatter", "name": "Class 1",
            "marker": {"size": 9, "color": "#FF6584", "line": {"width": 1, "color": "#fff"}},
        },
    ]
    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Decision Tree Boundary (Axis-Aligned Splits)", "font": {"size": 18}},
        "xaxis": {"title": "Feature 1", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Feature 2", "gridcolor": "rgba(255,255,255,0.08)"},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _overfitting_demo():
    """Show training vs validation accuracy with increasing tree depth."""
    depths = list(range(1, 21))
    np.random.seed(42)
    train_acc = [0.55 + 0.45 * (1 - np.exp(-0.4 * d)) for d in depths]
    val_acc = [0.55 + 0.35 * (1 - np.exp(-0.3 * d)) - 0.02 * max(0, d - 7) for d in depths]

    data = [
        {
            "x": depths, "y": train_acc,
            "mode": "lines+markers", "type": "scatter", "name": "Training Accuracy",
            "line": {"color": "#34d399", "width": 3},
            "marker": {"size": 6},
        },
        {
            "x": depths, "y": val_acc,
            "mode": "lines+markers", "type": "scatter", "name": "Validation Accuracy",
            "line": {"color": "#FF6584", "width": 3},
            "marker": {"size": 6},
        },
        {
            "x": [7, 7], "y": [0.5, 1.05],
            "mode": "lines", "type": "scatter", "name": "Optimal Depth",
            "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": "Overfitting with Increasing Depth", "font": {"size": 18}},
        "xaxis": {"title": "Max Depth", "gridcolor": "rgba(255,255,255,0.08)", "dtick": 2},
        "yaxis": {"title": "Accuracy", "gridcolor": "rgba(255,255,255,0.08)", "range": [0.5, 1.05]},
        "legend": {"x": 0.55, "y": 0.25},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def get_content() -> dict:
    return {
        "title": "Decision Trees",
        "icon": "🌳",
        "subtitle": "Interpretable models that learn if-else rules from data",
        "sections": [
            {
                "id": "introduction",
                "heading": "What are Decision Trees?",
                "type": "text",
                "content": (
                    "A Decision Tree is a flowchart-like model that makes predictions by recursively splitting data "
                    "based on feature values. Each internal node tests a condition, each branch represents the outcome, "
                    "and each leaf node holds a prediction.\n\n"
                    "Decision Trees are one of the most <strong>interpretable</strong> ML models — you can literally follow "
                    "the if-else logic to understand <em>why</em> a prediction was made."
                ),
            },
            {
                "id": "structure",
                "heading": "Tree Structure",
                "type": "graph",
                "description": (
                    "A simple decision tree with two splits. At each node, the tree asks a question about a feature. "
                    "The data flows left or right depending on the answer, until it reaches a leaf with a prediction."
                ),
                "graph_json": _tree_structure(),
            },
            {
                "id": "splitting",
                "heading": "How Splitting Works",
                "type": "graph",
                "description": (
                    "The tree chooses the best feature and threshold to split on by measuring <strong>impurity</strong>. "
                    "Lower impurity = purer groups. Entropy is used with Information Gain, while Gini is the default in sklearn."
                ),
                "graph_json": _gini_vs_entropy(),
            },
            {
                "id": "math",
                "heading": "The Mathematics",
                "type": "math",
                "content": [
                    {
                        "label": "Gini Impurity",
                        "formula": "G(t) = 1 - \\sum_{k=1}^{K} p_k^2",
                        "explanation": "Probability of misclassifying a randomly chosen element. Zero = pure node, 0.5 = maximum impurity for binary."
                    },
                    {
                        "label": "Entropy",
                        "formula": "H(t) = -\\sum_{k=1}^{K} p_k \\log_2(p_k)",
                        "explanation": "Measures the disorder/randomness in a node. Zero = pure node, 1.0 = maximum uncertainty for binary."
                    },
                    {
                        "label": "Information Gain",
                        "formula": "IG(t, a) = H(t) - \\sum_{v \\in Values(a)} \\frac{|t_v|}{|t|} H(t_v)",
                        "explanation": "The reduction in entropy after splitting on attribute a. The feature with the highest IG is chosen."
                    },
                ],
            },
            {
                "id": "boundary",
                "heading": "Decision Boundaries",
                "type": "graph",
                "description": (
                    "Decision trees create <strong>axis-aligned</strong> (rectangular) boundaries. "
                    "This is because each split tests one feature at a time. The boundaries can be complex enough to "
                    "capture non-linear patterns but are always made of horizontal and vertical lines."
                ),
                "graph_json": _decision_boundary(),
            },
            {
                "id": "overfitting",
                "heading": "Overfitting & Pruning",
                "type": "graph",
                "description": (
                    "Deep trees memorize training data. As depth increases, training accuracy keeps rising "
                    "but validation accuracy eventually drops — a clear sign of overfitting. "
                    "The yellow line shows the optimal depth for this dataset."
                ),
                "graph_json": _overfitting_demo(),
            },
            {
                "id": "implementation",
                "heading": "Python Implementation",
                "type": "code",
                "language": "python",
                "content": (
                    "from sklearn.tree import DecisionTreeClassifier, export_text\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 the model (with pruning)\n"
                    "model = DecisionTreeClassifier(\n"
                    "    max_depth=4,\n"
                    "    min_samples_split=5,\n"
                    "    random_state=42\n"
                    ")\n"
                    "model.fit(X_train, y_train)\n"
                    "\n"
                    "# Evaluate\n"
                    "y_pred = model.predict(X_test)\n"
                    "print(f'Accuracy: {accuracy_score(y_test, y_pred):.4f}')\n"
                    "\n"
                    "# View the tree rules\n"
                    "print(export_text(model, feature_names=iris.feature_names))\n"
                ),
            },
            {
                "id": "pros-cons",
                "heading": "Advantages & Disadvantages",
                "type": "list",
                "entries": [
                    {"title": "✅ Highly Interpretable", "detail": "You can visualize and explain every decision. Perfect for domains requiring transparency."},
                    {"title": "✅ No Feature Scaling", "detail": "Trees don't require normalization — they only care about relative ordering of values."},
                    {"title": "✅ Handles Non-Linear Data", "detail": "Can capture complex patterns that linear models miss."},
                    {"title": "❌ Prone to Overfitting", "detail": "Without pruning, trees grow until they memorize the training data. Always set max_depth."},
                    {"title": "❌ Unstable", "detail": "Small changes in data can produce a completely different tree. Ensemble methods (Random Forest) fix this."},
                    {"title": "❌ Biased with Imbalanced Data", "detail": "Trees favor the majority class. Use class_weight='balanced' to compensate."},
                ],
            },
        ],
    }
