"""
Generative Adversarial Networks (GANs) — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _gan_training():
    """Show GAN training dynamics — generator vs discriminator loss."""
    np.random.seed(42)
    epochs = list(range(1, 101))
    d_loss = [0.7 + 0.3 * np.exp(-0.03 * e) + np.random.normal(0, 0.04) for e in epochs]
    g_loss = [2.5 * np.exp(-0.04 * e) + 0.7 + np.random.normal(0, 0.06) for e in epochs]

    data = [
        {"x": epochs, "y": d_loss, "mode": "lines", "type": "scatter", "name": "Discriminator Loss",
         "line": {"color": "#FF6584", "width": 2.5}},
        {"x": epochs, "y": g_loss, "mode": "lines", "type": "scatter", "name": "Generator Loss",
         "line": {"color": "#6C63FF", "width": 2.5}},
        {"x": [1, 100], "y": [0.693, 0.693], "mode": "lines", "type": "scatter", "name": "Nash Equilibrium (ln2)",
         "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": "GAN Training Dynamics", "font": {"size": 18}},
        "xaxis": {"title": "Epoch", "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 _distribution_matching():
    """Show generator learning to match the real distribution."""
    x = np.linspace(-5, 5, 200)
    real = 0.6 * np.exp(-0.5 * ((x - 1) / 0.8) ** 2) + 0.4 * np.exp(-0.5 * ((x + 1.5) / 0.6) ** 2)
    gen_early = np.exp(-0.5 * (x / 2) ** 2) * 0.4
    gen_mid = 0.3 * np.exp(-0.5 * ((x - 0.5) / 1.2) ** 2) + 0.2 * np.exp(-0.5 * ((x + 1) / 1) ** 2)
    gen_late = 0.55 * np.exp(-0.5 * ((x - 1) / 0.85) ** 2) + 0.38 * np.exp(-0.5 * ((x + 1.5) / 0.65) ** 2)

    data = [
        {"x": x.tolist(), "y": real.tolist(), "mode": "lines", "type": "scatter", "name": "Real Distribution",
         "line": {"color": "#34d399", "width": 3}},
        {"x": x.tolist(), "y": gen_early.tolist(), "mode": "lines", "type": "scatter", "name": "Generator (Epoch 5)",
         "line": {"color": "#FF6584", "width": 2, "dash": "dot"}},
        {"x": x.tolist(), "y": gen_mid.tolist(), "mode": "lines", "type": "scatter", "name": "Generator (Epoch 50)",
         "line": {"color": "#fbbf24", "width": 2, "dash": "dash"}},
        {"x": x.tolist(), "y": gen_late.tolist(), "mode": "lines", "type": "scatter", "name": "Generator (Epoch 200)",
         "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": "Generator Learning the Real Distribution", "font": {"size": 18}},
        "xaxis": {"title": "Data Space", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Density", "gridcolor": "rgba(255,255,255,0.08)"},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def get_content() -> dict:
    return {
        "title": "GANs",
        "icon": "🎨",
        "subtitle": "Learning to generate realistic data through adversarial training",
        "sections": [
            {"id": "introduction", "heading": "What are GANs?", "type": "text",
             "content": ("Generative Adversarial Networks pit two neural networks against each other in a game:\n\n"
                         "The <strong>Generator</strong> creates fake data trying to fool the discriminator.\n"
                         "The <strong>Discriminator</strong> tries to distinguish real data from fake.\n\n"
                         "As they compete, the generator gets better at creating realistic data — images, music, text, "
                         "and more. GANs are behind deepfakes, AI art, and data augmentation.")},
            {"id": "how", "heading": "The Adversarial Game", "type": "list",
             "entries": [
                 {"title": "1. Sample Noise", "detail": "Draw random noise z from a simple distribution (usually Gaussian)."},
                 {"title": "2. Generate Fake Data", "detail": "Generator transforms noise into fake data: G(z). Starts as random noise, improves over training."},
                 {"title": "3. Discriminator Judges", "detail": "Discriminator sees both real data and G(z). Outputs probability of being real: D(x)."},
                 {"title": "4. Update Both", "detail": "Discriminator is trained to classify correctly. Generator is trained to maximize D(G(z)) — to fool the discriminator."},
             ]},
            {"id": "math", "heading": "The Mathematics", "type": "math",
             "content": [
                 {"label": "Minimax Objective", "formula": "\\min_G \\max_D \\; \\mathbb{E}_{x \\sim p_{data}}[\\log D(x)] + \\mathbb{E}_{z \\sim p_z}[\\log(1 - D(G(z)))]",
                  "explanation": "D maximizes its ability to distinguish real from fake. G minimizes D's success. At equilibrium, G produces data indistinguishable from real."},
                 {"label": "Generator Objective", "formula": "\\max_G \\; \\mathbb{E}_{z \\sim p_z}[\\log D(G(z))]",
                  "explanation": "In practice, maximize log D(G(z)) instead of minimizing log(1-D(G(z))) for better gradients early in training."},
                 {"label": "Nash Equilibrium", "formula": "D^*(x) = \\frac{p_{data}(x)}{p_{data}(x) + p_g(x)} = \\frac{1}{2}",
                  "explanation": "At the optimal point, the discriminator outputs 0.5 for everything — can't tell real from fake."},
             ]},
            {"id": "training", "heading": "Training Dynamics", "type": "graph",
             "description": "Both losses should stabilize around ln(2) ≈ 0.693 (Nash equilibrium). If D loss drops to 0, D is too strong (mode collapse risk). If G loss explodes, training has failed.",
             "graph_json": _gan_training()},
            {"id": "distribution", "heading": "Distribution Matching", "type": "graph",
             "description": "The generator starts producing random noise, then gradually learns to match the real data distribution. By epoch 200, the generated and real distributions nearly overlap.",
             "graph_json": _distribution_matching()},
            {"id": "variants", "heading": "Popular GAN Variants", "type": "list",
             "entries": [
                 {"title": "DCGAN", "detail": "Deep Convolutional GAN. Uses conv layers for both G and D. The baseline for image generation."},
                 {"title": "Wasserstein GAN (WGAN)", "detail": "Uses Wasserstein distance instead of JS divergence. Much more stable training. Gradient penalty variant (WGAN-GP)."},
                 {"title": "StyleGAN", "detail": "By NVIDIA. Produces incredibly realistic faces. Style-based generator enables fine control over features."},
                 {"title": "CycleGAN", "detail": "Unpaired image-to-image translation. Horse → Zebra, summer → winter, without paired examples."},
                 {"title": "Conditional GAN (cGAN)", "detail": "Conditions generation on a label or input. Enables controlled generation: 'generate a cat' or 'generate digit 7'."},
             ]},
            {"id": "implementation", "heading": "Python Implementation", "type": "code", "language": "python",
             "content": ("import torch\nimport torch.nn as nn\n\n"
                         "class Generator(nn.Module):\n    def __init__(self, latent_dim=100, img_channels=1):\n"
                         "        super().__init__()\n"
                         "        self.net = nn.Sequential(\n"
                         "            nn.Linear(latent_dim, 256),\n"
                         "            nn.LeakyReLU(0.2),\n"
                         "            nn.Linear(256, 512),\n"
                         "            nn.LeakyReLU(0.2),\n"
                         "            nn.Linear(512, 784),  # 28x28\n"
                         "            nn.Tanh(),\n"
                         "        )\n\n"
                         "    def forward(self, z):\n        return self.net(z).view(-1, 1, 28, 28)\n\n"
                         "class Discriminator(nn.Module):\n    def __init__(self):\n"
                         "        super().__init__()\n"
                         "        self.net = nn.Sequential(\n"
                         "            nn.Flatten(),\n"
                         "            nn.Linear(784, 512),\n"
                         "            nn.LeakyReLU(0.2),\n"
                         "            nn.Linear(512, 256),\n"
                         "            nn.LeakyReLU(0.2),\n"
                         "            nn.Linear(256, 1),\n"
                         "            nn.Sigmoid(),\n"
                         "        )\n\n"
                         "    def forward(self, x):\n        return self.net(x)\n\n"
                         "G = Generator()\nD = Discriminator()\n"
                         "print(f'Generator params: {sum(p.numel() for p in G.parameters()):,}')\n"
                         "print(f'Discriminator params: {sum(p.numel() for p in D.parameters()):,}')\n")},
            {"id": "tips", "heading": "Key Takeaways", "type": "list",
             "entries": [
                 {"title": "Training is Unstable", "detail": "GANs are notoriously hard to train. Use WGAN-GP, spectral normalization, and progressive growing."},
                 {"title": "Mode Collapse", "detail": "Generator produces very limited variety. Fix: minibatch discrimination, unrolled GANs, or diverse training."},
                 {"title": "Evaluation is Hard", "detail": "No single metric. FID (Fréchet Inception Distance) and IS (Inception Score) are commonly used."},
                 {"title": "Diffusion Models", "detail": "Diffusion models (DALL-E 2, Stable Diffusion) are now preferred over GANs for image generation — more stable training and better diversity."},
             ]},
        ],
    }
