"""
Backpropagation — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _chain_rule_visual():
    """Show the chain rule in a simple computational graph."""
    nodes = ["x", "w₁", "z₁=w₁x", "a₁=σ(z₁)", "w₂", "z₂=w₂a₁", "L=loss"]
    x_pos = [0, 0, 1.5, 3, 3, 4.5, 6]
    y_pos = [1, -1, 0, 0, -1.5, 0, 0]
    colors = ["#6C63FF", "#6C63FF", "#a78bfa", "#34d399", "#6C63FF", "#a78bfa", "#FF6584"]

    edge_x, edge_y = [], []
    edges = [(0, 2), (1, 2), (2, 3), (3, 5), (4, 5), (5, 6)]
    for i, j in edges:
        edge_x.extend([x_pos[i], x_pos[j], None])
        edge_y.extend([y_pos[i], y_pos[j], None])

    data = [
        {"x": edge_x, "y": edge_y, "mode": "lines", "type": "scatter",
         "line": {"color": "rgba(255,101,132,0.5)", "width": 2.5}, "showlegend": False},
        {"x": x_pos, "y": y_pos, "mode": "markers+text", "type": "scatter",
         "marker": {"size": 40, "color": colors, "line": {"width": 2, "color": "#fff"}},
         "text": nodes, "textposition": "top center",
         "textfont": {"size": 12, "color": "#e0e0e0"}, "showlegend": False},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Computational Graph (Forward & Backward)", "font": {"size": 18}},
        "xaxis": {"showgrid": False, "zeroline": False, "showticklabels": False},
        "yaxis": {"showgrid": False, "zeroline": False, "showticklabels": False, "scaleanchor": "x"},
        "margin": {"l": 20, "r": 20, "t": 50, "b": 20},
        "annotations": [{"x": 3, "y": -2.5, "text": "← Forward Pass (compute outputs) →", "showarrow": False,
                         "font": {"size": 12, "color": "#34d399"}},
                        {"x": 3, "y": -3.0, "text": "← Backward Pass (compute gradients) →", "showarrow": False,
                         "font": {"size": 12, "color": "#FF6584"}}],
    }
    return json.dumps({"data": data, "layout": layout})


def _gradient_flow():
    """Show gradient magnitudes through layers."""
    layers = ["Layer 5\n(output)", "Layer 4", "Layer 3", "Layer 2", "Layer 1\n(input)"]
    grad_normal = [1.0, 0.85, 0.72, 0.60, 0.50]
    grad_vanish = [1.0, 0.4, 0.16, 0.06, 0.02]
    grad_explode = [1.0, 2.5, 6.2, 15.5, 38.7]

    data = [
        {"y": layers, "x": grad_normal, "name": "Healthy Gradients", "type": "bar", "orientation": "h",
         "marker": {"color": "#34d399"}},
        {"y": layers, "x": grad_vanish, "name": "Vanishing Gradients", "type": "bar", "orientation": "h",
         "marker": {"color": "#FF6584"}},
        {"y": layers, "x": [min(g, 5) for g in grad_explode], "name": "Exploding Gradients (capped)", "type": "bar", "orientation": "h",
         "marker": {"color": "#fbbf24"}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Gradient Magnitude Through Layers", "font": {"size": 18}},
        "xaxis": {"title": "Relative Gradient Magnitude", "gridcolor": "rgba(255,255,255,0.08)"},
        "barmode": "group",
        "margin": {"l": 100, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _weight_update_demo():
    """Show gradient descent updating a weight over iterations."""
    np.random.seed(42)
    iters = list(range(50))
    w = 3.0
    lr = 0.1
    weights = []
    for _ in iters:
        weights.append(w)
        grad = 2 * (w - 1)  # gradient of (w-1)^2
        w -= lr * grad

    loss = [(w_val - 1) ** 2 for w_val in weights]

    data = [
        {"x": iters, "y": weights, "mode": "lines+markers", "type": "scatter", "name": "Weight Value",
         "line": {"color": "#6C63FF", "width": 3}, "marker": {"size": 4}, "yaxis": "y"},
        {"x": iters, "y": loss, "mode": "lines", "type": "scatter", "name": "Loss",
         "line": {"color": "#FF6584", "width": 2.5, "dash": "dash"}, "yaxis": "y2"},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Weight Converging to Optimal Value", "font": {"size": 18}},
        "xaxis": {"title": "Iteration", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Weight (w)", "gridcolor": "rgba(255,255,255,0.08)", "side": "left"},
        "yaxis2": {"title": "Loss", "overlaying": "y", "side": "right", "gridcolor": "rgba(255,255,255,0.04)"},
        "legend": {"x": 0.6, "y": 0.95},
        "margin": {"l": 60, "r": 60, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def get_content() -> dict:
    return {
        "title": "Backpropagation",
        "icon": "🔄",
        "subtitle": "How neural networks learn through gradient computation",
        "sections": [
            {"id": "introduction", "heading": "What is Backpropagation?", "type": "text",
             "content": ("Backpropagation is the <strong>core algorithm</strong> for training neural networks. It efficiently "
                         "computes the <strong>gradient of the loss</strong> with respect to every weight in the network using "
                         "the <strong>chain rule</strong> of calculus.\n\n"
                         "Without backprop, training deep networks would be computationally intractable. It enables gradient descent "
                         "to update millions of parameters in a single pass.")},
            {"id": "computational-graph", "heading": "Computational Graph", "type": "graph",
             "description": "The forward pass computes outputs left-to-right. The backward pass flows gradients right-to-left using the chain rule. Each node computes local gradients and passes them backward.",
             "graph_json": _chain_rule_visual()},
            {"id": "math", "heading": "The Mathematics", "type": "math",
             "content": [
                 {"label": "Chain Rule", "formula": "\\frac{\\partial L}{\\partial w} = \\frac{\\partial L}{\\partial a} \\cdot \\frac{\\partial a}{\\partial z} \\cdot \\frac{\\partial z}{\\partial w}",
                  "explanation": "The gradient of the loss w.r.t. a weight is the product of local gradients along the path from loss to that weight."},
                 {"label": "Weight Update", "formula": "w \\leftarrow w - \\eta \\frac{\\partial L}{\\partial w}",
                  "explanation": "Gradient descent: move each weight in the direction that reduces the loss. η is the learning rate."},
                 {"label": "Layer Gradient", "formula": "\\delta^{[l]} = (W^{[l+1]})^T \\delta^{[l+1]} \\circ \\sigma'(z^{[l]})",
                  "explanation": "Errors propagate backward: multiply by transposed weights and element-wise by the activation derivative."},
             ]},
            {"id": "weight-update", "heading": "Weight Convergence", "type": "graph",
             "description": "This demo shows a single weight being updated via gradient descent. It starts at w=3 and converges to the optimal value w=1 as the loss approaches zero.",
             "graph_json": _weight_update_demo()},
            {"id": "gradient-flow", "heading": "Gradient Flow Problems", "type": "graph",
             "description": "Gradients must flow through many layers. If they shrink at each layer (vanishing) or grow (exploding), training fails. Solutions: ReLU activation, batch normalization, residual connections.",
             "graph_json": _gradient_flow()},
            {"id": "algorithm", "heading": "The Algorithm Step by Step", "type": "list",
             "entries": [
                 {"title": "1. Forward Pass", "detail": "Feed input through all layers, computing and storing activations at each layer."},
                 {"title": "2. Compute Loss", "detail": "Compare the output to the true label using the loss function (e.g., cross-entropy)."},
                 {"title": "3. Backward Pass", "detail": "Starting from the loss, compute gradients for each layer using the chain rule, moving backward."},
                 {"title": "4. Update Weights", "detail": "Apply gradient descent: w = w - lr × gradient. Use an optimizer (Adam, SGD) for smarter updates."},
                 {"title": "5. Repeat", "detail": "Process the next mini-batch and repeat. One full pass through the dataset = one epoch."},
             ]},
            {"id": "solutions", "heading": "Solving Gradient Problems", "type": "list",
             "entries": [
                 {"title": "ReLU Activation", "detail": "Gradient is 1 for positive inputs (no shrinkage). Fixes vanishing gradients in hidden layers."},
                 {"title": "Batch Normalization", "detail": "Normalizes layer inputs to zero mean / unit variance. Stabilizes and accelerates training."},
                 {"title": "Residual Connections (Skip)", "detail": "y = F(x) + x. Gradients can flow directly through the skip connection. Enables 100+ layer networks."},
                 {"title": "Gradient Clipping", "detail": "Cap gradient magnitude to a max value. Essential for RNNs and transformers to prevent explosions."},
                 {"title": "Proper Initialization", "detail": "Xavier (sigmoid/tanh) or He (ReLU) initialization keeps gradients in a healthy range at the start."},
             ]},
        ],
    }
