"""
Bias-Variance Tradeoff — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _bias_variance_curve():
    """Show the classic bias-variance tradeoff curve."""
    complexity = np.linspace(1, 20, 100)
    bias_sq = 5 * np.exp(-0.3 * complexity) + 0.1
    variance = 0.05 * complexity ** 1.4
    total_error = bias_sq + variance + 0.3  # irreducible error = 0.3

    data = [
        {"x": complexity.tolist(), "y": bias_sq.tolist(), "mode": "lines", "type": "scatter", "name": "Bias²",
         "line": {"color": "#6C63FF", "width": 3}},
        {"x": complexity.tolist(), "y": variance.tolist(), "mode": "lines", "type": "scatter", "name": "Variance",
         "line": {"color": "#FF6584", "width": 3}},
        {"x": complexity.tolist(), "y": total_error.tolist(), "mode": "lines", "type": "scatter", "name": "Total Error",
         "line": {"color": "#fbbf24", "width": 3}},
        {"x": [1, 20], "y": [0.3, 0.3], "mode": "lines", "type": "scatter", "name": "Irreducible Error",
         "line": {"color": "#34d399", "width": 2, "dash": "dash"}},
    ]
    # Find optimal
    opt_idx = np.argmin(total_error)
    data.append({"x": [complexity[opt_idx]], "y": [total_error[opt_idx]],
                 "mode": "markers", "type": "scatter", "name": "Optimal Complexity",
                 "marker": {"size": 14, "color": "#fbbf24", "symbol": "star"}})

    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Bias-Variance Tradeoff", "font": {"size": 18}},
        "xaxis": {"title": "Model Complexity →", "gridcolor": "rgba(255,255,255,0.08)", "showticklabels": False},
        "yaxis": {"title": "Error", "gridcolor": "rgba(255,255,255,0.08)"},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _underfitting_overfitting():
    """Show model fits: underfit, good fit, overfit."""
    np.random.seed(42)
    x = np.linspace(0, 6, 30)
    y_true = np.sin(x) * 2 + 0.5 * x
    y = y_true + np.random.randn(30) * 0.5

    x_smooth = np.linspace(0, 6, 200)
    # Underfit (degree 1)
    c1 = np.polyfit(x, y, 1)
    y_under = np.polyval(c1, x_smooth)
    # Good fit (degree 3)
    c3 = np.polyfit(x, y, 3)
    y_good = np.polyval(c3, x_smooth)
    # Overfit (degree 15)
    c15 = np.polyfit(x, y, 15)
    y_over = np.polyval(c15, x_smooth)
    y_over = np.clip(y_over, -5, 10)

    data = [
        {"x": x.tolist(), "y": y.tolist(), "mode": "markers", "type": "scatter", "name": "Data",
         "marker": {"size": 9, "color": "#a78bfa", "line": {"width": 1, "color": "#fff"}}},
        {"x": x_smooth.tolist(), "y": y_under.tolist(), "mode": "lines", "type": "scatter", "name": "Underfit (degree 1)",
         "line": {"color": "#FF6584", "width": 2.5}},
        {"x": x_smooth.tolist(), "y": y_good.tolist(), "mode": "lines", "type": "scatter", "name": "Good Fit (degree 3)",
         "line": {"color": "#34d399", "width": 3}},
        {"x": x_smooth.tolist(), "y": y_over.tolist(), "mode": "lines", "type": "scatter", "name": "Overfit (degree 15)",
         "line": {"color": "#fbbf24", "width": 2.5}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Underfitting vs Good Fit vs Overfitting", "font": {"size": 18}},
        "xaxis": {"title": "x", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "y", "gridcolor": "rgba(255,255,255,0.08)", "range": [-3, 8]},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _train_val_gap():
    """Show training vs validation error curves."""
    train_sizes = np.linspace(10, 200, 50).astype(int).tolist()
    train_err = [0.3 / (1 + 0.005 * n) + 0.01 for n in train_sizes]
    val_err = [0.8 * np.exp(-0.01 * n) + 0.15 for n in train_sizes]

    data = [
        {"x": train_sizes, "y": train_err, "mode": "lines", "type": "scatter", "name": "Training Error",
         "line": {"color": "#34d399", "width": 3}},
        {"x": train_sizes, "y": val_err, "mode": "lines", "type": "scatter", "name": "Validation Error",
         "line": {"color": "#FF6584", "width": 3}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Learning Curves", "font": {"size": 18}},
        "xaxis": {"title": "Training Set Size", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Error", "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": "Bias-Variance Tradeoff",
        "icon": "⚖️",
        "subtitle": "The fundamental challenge of machine learning",
        "sections": [
            {"id": "introduction", "heading": "What is Bias-Variance Tradeoff?", "type": "text",
             "content": ("Every ML model navigates a fundamental tension between two sources of error:\n\n"
                         "<strong>Bias</strong> — error from wrong assumptions. High bias means the model is too simple and "
                         "<strong>underfits</strong> the data.\n\n"
                         "<strong>Variance</strong> — error from sensitivity to training data. High variance means the model "
                         "<strong>overfits</strong> and doesn't generalize.")},
            {"id": "tradeoff", "heading": "The Tradeoff Curve", "type": "graph",
             "description": "As model complexity increases, bias decreases but variance increases. The total error has a minimum — the sweet spot of good generalization. The green dashed line is irreducible noise.",
             "graph_json": _bias_variance_curve()},
            {"id": "math", "heading": "The Mathematics", "type": "math",
             "content": [
                 {"label": "Expected Error Decomposition", "formula": "\\text{Error}(x) = \\text{Bias}^2 + \\text{Variance} + \\sigma^2",
                  "explanation": "Total error = squared bias + variance + irreducible noise. We cannot reduce σ² (noise in the data)."},
                 {"label": "Bias", "formula": "\\text{Bias}[\\hat{f}(x)] = E[\\hat{f}(x)] - f(x)",
                  "explanation": "How far the average prediction is from the true value. Systematic error."},
                 {"label": "Variance", "formula": "\\text{Var}[\\hat{f}(x)] = E\\left[(\\hat{f}(x) - E[\\hat{f}(x)])^2\\right]",
                  "explanation": "How much predictions vary across different training sets. Sensitivity to data."},
             ]},
            {"id": "visualization", "heading": "Visual Examples", "type": "graph",
             "description": "A linear model (degree 1) underfits — it can't capture the curve. A degree-3 polynomial fits well. A degree-15 polynomial passes through every point but oscillates wildly on new data.",
             "graph_json": _underfitting_overfitting()},
            {"id": "learning-curves", "heading": "Learning Curves", "type": "graph",
             "description": "If training and validation errors converge → high bias (need more complex model). If there's a large gap → high variance (need more data or regularization).",
             "graph_json": _train_val_gap()},
            {"id": "diagnosis", "heading": "Diagnosing Your Model", "type": "list",
             "entries": [
                 {"title": "High Bias (Underfitting)", "detail": "Symptoms: High training error, similar validation error. Fix: More features, less regularization, more complex model."},
                 {"title": "High Variance (Overfitting)", "detail": "Symptoms: Low training error, much higher validation error. Fix: More data, regularization, simpler model, dropout, pruning."},
                 {"title": "High Bias + High Variance", "detail": "Worst case — model is both wrong and unstable. Usually means broken feature set or wrong algorithm choice."},
                 {"title": "Low Bias + Low Variance", "detail": "The goal! Training and validation errors are both low and close together."},
             ]},
            {"id": "models", "heading": "Where Do Models Fall?", "type": "list",
             "entries": [
                 {"title": "High Bias Models", "detail": "Linear Regression, Logistic Regression, Naive Bayes. Simple, interpretable, but may miss complex patterns."},
                 {"title": "High Variance Models", "detail": "Deep Decision Trees, KNN with K=1, High-degree polynomials. Flexible, but prone to memorizing noise."},
                 {"title": "Balanced Models", "detail": "Random Forest, Gradient Boosting, Neural Networks with regularization. Good default choices for most problems."},
             ]},
        ],
    }
