"""
Support Vector Machines (SVM) — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _linear_svm():
    """Generate a linear SVM with margin visualization."""
    np.random.seed(42)
    n = 50
    X0 = np.random.randn(n, 2) * 0.8 + np.array([-1.8, -1.2])
    X1 = np.random.randn(n, 2) * 0.8 + np.array([1.8, 1.2])

    xx = np.linspace(-5, 5, 100)
    yy = -xx  # decision boundary
    yy_up = -xx + 1.8   # upper margin
    yy_down = -xx - 1.8  # lower margin

    data = [
        {"x": X0[:, 0].tolist(), "y": X0[:, 1].tolist(), "mode": "markers", "type": "scatter", "name": "Class -1",
         "marker": {"size": 9, "color": "#6C63FF", "line": {"width": 1, "color": "#fff"}}},
        {"x": X1[:, 0].tolist(), "y": X1[:, 1].tolist(), "mode": "markers", "type": "scatter", "name": "Class +1",
         "marker": {"size": 9, "color": "#FF6584", "line": {"width": 1, "color": "#fff"}}},
        {"x": xx.tolist(), "y": yy.tolist(), "mode": "lines", "type": "scatter", "name": "Hyperplane",
         "line": {"color": "#fbbf24", "width": 3}},
        {"x": xx.tolist(), "y": yy_up.tolist(), "mode": "lines", "type": "scatter", "name": "Margin",
         "line": {"color": "#fbbf24", "width": 1.5, "dash": "dash"}},
        {"x": xx.tolist(), "y": yy_down.tolist(), "mode": "lines", "type": "scatter", "showlegend": False,
         "line": {"color": "#fbbf24", "width": 1.5, "dash": "dash"}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Linear SVM with Maximum Margin", "font": {"size": 18}},
        "xaxis": {"title": "Feature 1", "gridcolor": "rgba(255,255,255,0.08)", "range": [-5, 5]},
        "yaxis": {"title": "Feature 2", "gridcolor": "rgba(255,255,255,0.08)", "range": [-5, 5]},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _kernel_trick():
    """Show how kernel trick maps non-linear data to higher dimensions."""
    np.random.seed(42)
    theta = np.linspace(0, 2 * np.pi, 80)
    r1 = 1 + np.random.randn(80) * 0.15
    r2 = 2.5 + np.random.randn(80) * 0.15
    X0 = np.column_stack([r1 * np.cos(theta), r1 * np.sin(theta)])
    X1 = np.column_stack([r2 * np.cos(theta), r2 * np.sin(theta)])

    circle_t = np.linspace(0, 2 * np.pi, 200)
    boundary_r = 1.75
    bx = boundary_r * np.cos(circle_t)
    by = boundary_r * np.sin(circle_t)

    data = [
        {"x": X0[:, 0].tolist(), "y": X0[:, 1].tolist(), "mode": "markers", "type": "scatter", "name": "Class 0 (inner)",
         "marker": {"size": 8, "color": "#6C63FF", "line": {"width": 1, "color": "#fff"}}},
        {"x": X1[:, 0].tolist(), "y": X1[:, 1].tolist(), "mode": "markers", "type": "scatter", "name": "Class 1 (outer)",
         "marker": {"size": 8, "color": "#FF6584", "line": {"width": 1, "color": "#fff"}}},
        {"x": bx.tolist(), "y": by.tolist(), "mode": "lines", "type": "scatter", "name": "RBF Kernel Boundary",
         "line": {"color": "#34d399", "width": 3}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Non-Linear SVM with RBF Kernel", "font": {"size": 18}},
        "xaxis": {"title": "Feature 1", "gridcolor": "rgba(255,255,255,0.08)", "scaleanchor": "y"},
        "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 _c_parameter():
    """Show effect of C parameter on decision boundary."""
    c_values = np.logspace(-2, 3, 50)
    train_acc = [0.75 + 0.24 * (1 - np.exp(-0.5 * c)) for c in c_values]
    val_acc = [0.75 + 0.22 * (1 - np.exp(-0.3 * c)) - 0.08 * max(0, np.log10(c) - 1) for c in c_values]

    data = [
        {"x": c_values.tolist(), "y": train_acc, "mode": "lines", "type": "scatter", "name": "Training Accuracy",
         "line": {"color": "#34d399", "width": 3}},
        {"x": c_values.tolist(), "y": val_acc, "mode": "lines", "type": "scatter", "name": "Validation Accuracy",
         "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": "Effect of C Parameter", "font": {"size": 18}},
        "xaxis": {"title": "C (Regularization)", "type": "log", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Accuracy", "gridcolor": "rgba(255,255,255,0.08)", "range": [0.7, 1.02]},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def get_content() -> dict:
    return {
        "title": "Support Vector Machines",
        "icon": "🎯",
        "subtitle": "Finding the optimal separating hyperplane",
        "sections": [
            {
                "id": "introduction", "heading": "What is an SVM?", "type": "text",
                "content": (
                    "A Support Vector Machine finds the <strong>hyperplane</strong> that separates classes with the "
                    "<strong>maximum margin</strong> — the widest possible gap between the two classes. "
                    "The data points closest to this boundary are called <strong>support vectors</strong>.\n\n"
                    "SVMs are powerful for both linear and non-linear classification thanks to the <strong>kernel trick</strong>."
                ),
            },
            {"id": "linear", "heading": "Maximum Margin Classification", "type": "graph",
             "description": "The SVM finds the line (hyperplane) with the widest margin between classes. The dashed lines show the margin boundaries. Support vectors sit on these boundaries.",
             "graph_json": _linear_svm()},
            {"id": "kernel", "heading": "The Kernel Trick", "type": "graph",
             "description": "When data isn't linearly separable, the kernel trick maps it into a higher-dimensional space where a linear boundary exists. The RBF (Gaussian) kernel can create circular and complex boundaries.",
             "graph_json": _kernel_trick()},
            {
                "id": "math", "heading": "The Mathematics", "type": "math",
                "content": [
                    {"label": "Optimization Objective", "formula": "\\min_{w,b} \\frac{1}{2} \\|w\\|^2 + C \\sum_{i=1}^{m} \\xi_i",
                     "explanation": "Minimize the weight vector norm (maximize margin) while allowing some misclassifications (slack variables ξ) controlled by C."},
                    {"label": "Constraint", "formula": "y_i(w \\cdot x_i + b) \\geq 1 - \\xi_i, \\quad \\xi_i \\geq 0",
                     "explanation": "Each point must be on the correct side of the margin, or within a slack tolerance."},
                    {"label": "RBF Kernel", "formula": "K(x_i, x_j) = \\exp\\left(-\\gamma \\|x_i - x_j\\|^2\\right)",
                     "explanation": "Most popular non-linear kernel. γ controls how far the influence of a single point reaches."},
                ],
            },
            {"id": "c-param", "heading": "Regularization Parameter C", "type": "graph",
             "description": "C controls the tradeoff between a wide margin and misclassifications. Small C = wider margin but more errors. Large C = narrow margin but fewer errors (risk of overfitting).",
             "graph_json": _c_parameter()},
            {
                "id": "kernels", "heading": "Common Kernels", "type": "list",
                "entries": [
                    {"title": "Linear", "detail": "K(x, y) = x · y. Use when data is linearly separable or high-dimensional (text classification)."},
                    {"title": "Polynomial", "detail": "K(x, y) = (γ·x·y + r)^d. Captures feature interactions. Degree d controls complexity."},
                    {"title": "RBF (Gaussian)", "detail": "Most versatile. Works well in most cases. Tune γ carefully — too high = overfitting."},
                    {"title": "Sigmoid", "detail": "K(x, y) = tanh(γ·x·y + r). Behaves like a neural network. Rarely used in practice."},
                ],
            },
            {
                "id": "implementation", "heading": "Python Implementation", "type": "code", "language": "python",
                "content": (
                    "from sklearn.svm import SVC\n"
                    "from sklearn.preprocessing import StandardScaler\n"
                    "from sklearn.model_selection import train_test_split\n"
                    "from sklearn.datasets import make_moons\n"
                    "from sklearn.metrics import accuracy_score\n\n"
                    "# Generate non-linear data\n"
                    "X, y = make_moons(n_samples=300, noise=0.2, random_state=42)\n\n"
                    "# Scale features (critical for SVM!)\n"
                    "scaler = StandardScaler()\n"
                    "X_scaled = scaler.fit_transform(X)\n\n"
                    "# Train-test split\n"
                    "X_train, X_test, y_train, y_test = train_test_split(\n"
                    "    X_scaled, y, test_size=0.2, random_state=42\n)\n\n"
                    "# Fit SVM with RBF kernel\n"
                    "model = SVC(kernel='rbf', C=1.0, gamma='scale')\n"
                    "model.fit(X_train, y_train)\n\n"
                    "# Evaluate\n"
                    "print(f'Accuracy: {accuracy_score(y_test, model.predict(X_test)):.4f}')\n"
                    "print(f'Support Vectors: {model.n_support_}')\n"
                ),
            },
            {
                "id": "tips", "heading": "Key Takeaways", "type": "list",
                "entries": [
                    {"title": "Always Scale Features", "detail": "SVMs are distance-based. Without scaling, features with larger ranges dominate."},
                    {"title": "Start with RBF", "detail": "RBF kernel is a good default. Only switch to linear if you have very high-dimensional data."},
                    {"title": "Tune C and γ Together", "detail": "Use GridSearchCV with a log scale for both. They interact strongly."},
                    {"title": "Not Great for Large Datasets", "detail": "Training time is O(n²) to O(n³). For 100K+ samples, consider LinearSVC or SGDClassifier."},
                ],
            },
        ],
    }
