"""
Recurrent Neural Networks & LSTMs — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _rnn_unrolled():
    """Show an unrolled RNN processing a sequence."""
    steps = ["t-2", "t-1", "t", "t+1", "t+2"]
    x_pos = [i * 3 for i in range(5)]
    hidden_y = [1.5] * 5
    input_y = [0] * 5
    output_y = [3] * 5

    edge_x, edge_y = [], []
    for i in range(4):
        edge_x.extend([x_pos[i], x_pos[i+1], None])
        edge_y.extend([hidden_y[i], hidden_y[i+1], None])
    for i in range(5):
        edge_x.extend([x_pos[i], x_pos[i], None])
        edge_y.extend([input_y[i], hidden_y[i], None])
        edge_x.extend([x_pos[i], x_pos[i], None])
        edge_y.extend([hidden_y[i], output_y[i], None])

    data = [
        {"x": edge_x, "y": edge_y, "mode": "lines", "type": "scatter",
         "line": {"color": "rgba(167,139,250,0.4)", "width": 2}, "showlegend": False},
        {"x": x_pos, "y": hidden_y, "mode": "markers+text", "type": "scatter", "name": "Hidden State (hₜ)",
         "marker": {"size": 35, "color": "#6C63FF", "line": {"width": 2, "color": "#fff"}},
         "text": ["h₀", "h₁", "h₂", "h₃", "h₄"], "textfont": {"color": "#fff", "size": 12}, "textposition": "middle center"},
        {"x": x_pos, "y": input_y, "mode": "markers+text", "type": "scatter", "name": "Input (xₜ)",
         "marker": {"size": 28, "color": "#34d399", "line": {"width": 2, "color": "#fff"}},
         "text": steps, "textfont": {"color": "#fff", "size": 11}, "textposition": "bottom center"},
        {"x": x_pos, "y": output_y, "mode": "markers+text", "type": "scatter", "name": "Output (yₜ)",
         "marker": {"size": 28, "color": "#FF6584", "line": {"width": 2, "color": "#fff"}},
         "text": ["y₀", "y₁", "y₂", "y₃", "y₄"], "textfont": {"color": "#fff", "size": 11}, "textposition": "top center"},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Unrolled RNN Over Time Steps", "font": {"size": 18}},
        "xaxis": {"showgrid": False, "zeroline": False, "showticklabels": False},
        "yaxis": {"showgrid": False, "zeroline": False, "showticklabels": False, "range": [-1, 4]},
        "margin": {"l": 20, "r": 20, "t": 50, "b": 20},
    }
    return json.dumps({"data": data, "layout": layout})


def _lstm_gates():
    """Show LSTM gate activations over time."""
    np.random.seed(42)
    t = list(range(20))
    forget = [0.9 - 0.3 * np.sin(i * 0.5) + np.random.normal(0, 0.05) for i in t]
    input_g = [0.3 + 0.4 * np.cos(i * 0.4) + np.random.normal(0, 0.05) for i in t]
    output_g = [0.6 + 0.2 * np.sin(i * 0.3) + np.random.normal(0, 0.05) for i in t]
    cell = np.cumsum([0.1 * (ig - fg * 0.5) for ig, fg in zip(input_g, forget)]).tolist()

    data = [
        {"x": t, "y": forget, "mode": "lines", "type": "scatter", "name": "Forget Gate",
         "line": {"color": "#FF6584", "width": 2.5}},
        {"x": t, "y": input_g, "mode": "lines", "type": "scatter", "name": "Input Gate",
         "line": {"color": "#34d399", "width": 2.5}},
        {"x": t, "y": output_g, "mode": "lines", "type": "scatter", "name": "Output Gate",
         "line": {"color": "#6C63FF", "width": 2.5}},
        {"x": t, "y": cell, "mode": "lines", "type": "scatter", "name": "Cell State",
         "line": {"color": "#fbbf24", "width": 3, "dash": "dash"}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "LSTM Gate Activations Over Time", "font": {"size": 18}},
        "xaxis": {"title": "Time Step", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Activation / State", "gridcolor": "rgba(255,255,255,0.08)"},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _vanishing_gradient_rnn():
    """Show gradient magnitude decay in vanilla RNN vs LSTM."""
    steps_back = list(range(1, 21))
    rnn_grad = [1.0 * (0.7 ** s) for s in steps_back]
    lstm_grad = [1.0 * (0.95 ** s) for s in steps_back]

    data = [
        {"x": steps_back, "y": rnn_grad, "mode": "lines+markers", "type": "scatter", "name": "Vanilla RNN",
         "line": {"color": "#FF6584", "width": 3}, "marker": {"size": 5}},
        {"x": steps_back, "y": lstm_grad, "mode": "lines+markers", "type": "scatter", "name": "LSTM",
         "line": {"color": "#34d399", "width": 3}, "marker": {"size": 5}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Gradient Magnitude vs Sequence Length", "font": {"size": 18}},
        "xaxis": {"title": "Steps Back in Time", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Gradient Magnitude", "gridcolor": "rgba(255,255,255,0.08)", "type": "log"},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def get_content() -> dict:
    return {
        "title": "RNNs & LSTMs",
        "icon": "🔁",
        "subtitle": "Neural networks for sequential and time-series data",
        "sections": [
            {"id": "introduction", "heading": "What are RNNs?", "type": "text",
             "content": ("Recurrent Neural Networks process <strong>sequential data</strong> by maintaining a <strong>hidden state</strong> "
                         "that acts as memory. At each time step, the network reads a new input and updates its hidden state.\n\n"
                         "This makes RNNs ideal for text, speech, music, time series, and any data where <strong>order matters</strong>.")},
            {"id": "unrolled", "heading": "Unrolled RNN", "type": "graph",
             "description": "An RNN is the same network applied repeatedly at each time step. The hidden state (blue) carries information forward. Green nodes are inputs, red nodes are outputs.",
             "graph_json": _rnn_unrolled()},
            {"id": "math", "heading": "The Mathematics", "type": "math",
             "content": [
                 {"label": "Vanilla RNN", "formula": "h_t = \\tanh(W_{hh} h_{t-1} + W_{xh} x_t + b_h)",
                  "explanation": "The hidden state is updated by combining the previous hidden state and current input through tanh."},
                 {"label": "LSTM — Forget Gate", "formula": "f_t = \\sigma(W_f [h_{t-1}, x_t] + b_f)",
                  "explanation": "Decides what information to DISCARD from the cell state. Output 0 = forget, 1 = keep."},
                 {"label": "LSTM — Input Gate", "formula": "i_t = \\sigma(W_i [h_{t-1}, x_t] + b_i) \\quad \\tilde{C}_t = \\tanh(W_C [h_{t-1}, x_t] + b_C)",
                  "explanation": "Decides what NEW information to store. The sigmoid decides which values to update; tanh creates candidate values."},
                 {"label": "LSTM — Cell Update", "formula": "C_t = f_t \\circ C_{t-1} + i_t \\circ \\tilde{C}_t",
                  "explanation": "The cell state is updated: forget old info + add new info. This is the key to long-term memory."},
             ]},
            {"id": "lstm-gates", "heading": "LSTM Gates in Action", "type": "graph",
             "description": "LSTM has 3 gates controlling information flow. The forget gate (red) controls what to drop, the input gate (green) what to add, and the output gate (blue) what to expose. The cell state (yellow) is the long-term memory.",
             "graph_json": _lstm_gates()},
            {"id": "vanishing", "heading": "Why LSTM Beats Vanilla RNN", "type": "graph",
             "description": "Vanilla RNNs lose gradient signal exponentially fast over long sequences (vanishing gradients). LSTMs maintain gradient flow through the cell state's additive update, enabling learning over 100+ time steps.",
             "graph_json": _vanishing_gradient_rnn()},
            {"id": "variants", "heading": "RNN Variants", "type": "list",
             "entries": [
                 {"title": "Vanilla RNN", "detail": "Simple but suffers from vanishing gradients. Can only remember ~10-20 steps back."},
                 {"title": "LSTM", "detail": "Long Short-Term Memory. 3 gates + cell state. The standard for sequence modeling. Handles 100+ step dependencies."},
                 {"title": "GRU", "detail": "Gated Recurrent Unit. Simplified LSTM with 2 gates (reset, update). Similar performance, fewer parameters."},
                 {"title": "Bidirectional", "detail": "Processes sequence in both directions. The forward and backward hidden states are concatenated. Better for NLP tasks."},
                 {"title": "Seq2Seq (Encoder-Decoder)", "detail": "Encoder RNN compresses input to a context vector. Decoder RNN generates output sequence. Used in translation."},
             ]},
            {"id": "implementation", "heading": "Python Implementation", "type": "code", "language": "python",
             "content": ("import torch\nimport torch.nn as nn\n\nclass LSTMModel(nn.Module):\n"
                         "    def __init__(self, input_size, hidden_size, num_layers, num_classes):\n"
                         "        super().__init__()\n"
                         "        self.lstm = nn.LSTM(\n"
                         "            input_size, hidden_size, num_layers,\n"
                         "            batch_first=True, dropout=0.3,\n"
                         "            bidirectional=True\n"
                         "        )\n"
                         "        self.fc = nn.Linear(hidden_size * 2, num_classes)  # *2 for bidirectional\n\n"
                         "    def forward(self, x):\n"
                         "        # x shape: (batch, seq_len, input_size)\n"
                         "        out, (h_n, c_n) = self.lstm(x)\n"
                         "        # Use last time step output\n"
                         "        out = self.fc(out[:, -1, :])\n"
                         "        return out\n\n"
                         "model = LSTMModel(input_size=10, hidden_size=64,\n"
                         "                  num_layers=2, num_classes=5)\n"
                         "print(f'Parameters: {sum(p.numel() for p in model.parameters()):,}')\n")},
            {"id": "tips", "heading": "Key Takeaways", "type": "list",
             "entries": [
                 {"title": "LSTM > Vanilla RNN", "detail": "Always use LSTM or GRU over vanilla RNN. No extra cost, much better long-term memory."},
                 {"title": "Transformers Are Taking Over", "detail": "For text/NLP, Transformers (BERT, GPT) now outperform RNNs. But LSTMs are still great for time series."},
                 {"title": "Gradient Clipping", "detail": "Always use gradient clipping (max_norm=1.0) to prevent exploding gradients in RNNs."},
                 {"title": "Pack Padded Sequences", "detail": "Use pack_padded_sequence in PyTorch when sequences have different lengths. Avoids wasting computation on padding."},
             ]},
        ],
    }
