"""
Linear Regression — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _scatter_with_regression():
    """Generate a scatter plot with best-fit regression line."""
    np.random.seed(42)
    X = np.linspace(1, 10, 40)
    y = 2.5 * X + 3 + np.random.normal(0, 2.5, size=len(X))

    # Compute regression coefficients
    m, b = np.polyfit(X, y, 1)
    y_pred = m * X + b

    data = [
        {
            "x": X.tolist(),
            "y": y.tolist(),
            "mode": "markers",
            "type": "scatter",
            "name": "Data Points",
            "marker": {
                "size": 10,
                "color": "#6C63FF",
                "opacity": 0.85,
                "line": {"width": 1, "color": "#fff"}
            },
        },
        {
            "x": X.tolist(),
            "y": y_pred.tolist(),
            "mode": "lines",
            "type": "scatter",
            "name": f"Best Fit: y = {m:.2f}x + {b:.2f}",
            "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": "Scatter Plot with Best-Fit Line", "font": {"size": 18}},
        "xaxis": {"title": "X (Feature)", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "y (Target)", "gridcolor": "rgba(255,255,255,0.08)"},
        "legend": {"x": 0.02, "y": 0.98},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _cost_function_surface():
    """Generate a 3D surface plot of the MSE cost function."""
    np.random.seed(42)
    X = np.linspace(1, 10, 40)
    y = 2.5 * X + 3 + np.random.normal(0, 2.5, size=len(X))

    m_range = np.linspace(-1, 6, 60)
    b_range = np.linspace(-5, 10, 60)
    M, B = np.meshgrid(m_range, b_range)
    Z = np.zeros_like(M)

    for i in range(len(m_range)):
        for j in range(len(b_range)):
            pred = M[j, i] * X + B[j, i]
            Z[j, i] = np.mean((y - pred) ** 2)

    data = [
        {
            "z": Z.tolist(),
            "x": m_range.tolist(),
            "y": b_range.tolist(),
            "type": "surface",
            "colorscale": [
                [0, "#6C63FF"],
                [0.3, "#a78bfa"],
                [0.6, "#FF6584"],
                [1, "#fbbf24"]
            ],
            "opacity": 0.92,
        }
    ]
    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "MSE Cost Function Surface", "font": {"size": 18}},
        "scene": {
            "xaxis": {"title": "Slope (m)", "gridcolor": "rgba(255,255,255,0.08)"},
            "yaxis": {"title": "Intercept (b)", "gridcolor": "rgba(255,255,255,0.08)"},
            "zaxis": {"title": "MSE", "gridcolor": "rgba(255,255,255,0.08)"},
            "bgcolor": "rgba(0,0,0,0)",
        },
        "margin": {"l": 0, "r": 0, "t": 50, "b": 0},
    }
    return json.dumps({"data": data, "layout": layout})


def _gradient_descent_animation():
    """Generate a contour plot showing gradient descent path."""
    np.random.seed(42)
    X = np.linspace(1, 10, 40)
    y = 2.5 * X + 3 + np.random.normal(0, 2.5, size=len(X))

    m_range = np.linspace(-1, 6, 80)
    b_range = np.linspace(-5, 10, 80)
    M, B = np.meshgrid(m_range, b_range)
    Z = np.zeros_like(M)

    for i in range(len(m_range)):
        for j in range(len(b_range)):
            pred = M[j, i] * X + B[j, i]
            Z[j, i] = np.mean((y - pred) ** 2)

    # Simulate gradient descent
    lr = 0.01
    m_gd, b_gd = 0.0, 0.0
    path_m, path_b = [m_gd], [b_gd]
    for _ in range(80):
        pred = m_gd * X + b_gd
        dm = -2 * np.mean(X * (y - pred))
        db = -2 * np.mean(y - pred)
        m_gd -= lr * dm
        b_gd -= lr * db
        path_m.append(m_gd)
        path_b.append(b_gd)

    data = [
        {
            "z": Z.tolist(),
            "x": m_range.tolist(),
            "y": b_range.tolist(),
            "type": "contour",
            "colorscale": [
                [0, "#0f0c29"],
                [0.25, "#302b63"],
                [0.5, "#6C63FF"],
                [0.75, "#a78bfa"],
                [1, "#fbbf24"]
            ],
            "contours": {"coloring": "heatmap"},
            "line": {"width": 0.5, "color": "rgba(255,255,255,0.15)"},
            "showscale": False,
        },
        {
            "x": path_m,
            "y": path_b,
            "mode": "lines+markers",
            "type": "scatter",
            "name": "Gradient Descent Path",
            "line": {"color": "#FF6584", "width": 2},
            "marker": {"size": 4, "color": "#FF6584"},
        },
        {
            "x": [path_m[0]],
            "y": [path_b[0]],
            "mode": "markers",
            "type": "scatter",
            "name": "Start",
            "marker": {"size": 14, "color": "#fbbf24", "symbol": "star"},
        },
        {
            "x": [path_m[-1]],
            "y": [path_b[-1]],
            "mode": "markers",
            "type": "scatter",
            "name": "Converged",
            "marker": {"size": 14, "color": "#34d399", "symbol": "diamond"},
        },
    ]
    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Gradient Descent on MSE Contour", "font": {"size": 18}},
        "xaxis": {"title": "Slope (m)", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Intercept (b)", "gridcolor": "rgba(255,255,255,0.08)"},
        "legend": {"x": 0.02, "y": 0.98},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _residual_plot():
    """Generate a residual plot to show model fit quality."""
    np.random.seed(42)
    X = np.linspace(1, 10, 40)
    y = 2.5 * X + 3 + np.random.normal(0, 2.5, size=len(X))
    m, b = np.polyfit(X, y, 1)
    y_pred = m * X + b
    residuals = y - y_pred

    data = [
        {
            "x": X.tolist(),
            "y": residuals.tolist(),
            "mode": "markers",
            "type": "scatter",
            "name": "Residuals",
            "marker": {
                "size": 10,
                "color": residuals.tolist(),
                "colorscale": [[0, "#6C63FF"], [0.5, "#a78bfa"], [1, "#FF6584"]],
                "showscale": True,
                "colorbar": {"title": "Residual"},
                "line": {"width": 1, "color": "#fff"}
            },
        },
        {
            "x": [X.min(), X.max()],
            "y": [0, 0],
            "mode": "lines",
            "type": "scatter",
            "name": "Zero Line",
            "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": "Residual Plot", "font": {"size": 18}},
        "xaxis": {"title": "X (Feature)", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Residual (y - ŷ)", "gridcolor": "rgba(255,255,255,0.08)"},
        "legend": {"x": 0.02, "y": 0.98},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _metrics_bar_chart():
    """Generate a bar chart comparing evaluation metrics."""
    np.random.seed(42)
    X = np.linspace(1, 10, 40)
    y = 2.5 * X + 3 + np.random.normal(0, 2.5, size=len(X))
    m, b = np.polyfit(X, y, 1)
    y_pred = m * X + b

    mse = float(np.mean((y - y_pred) ** 2))
    rmse = float(np.sqrt(mse))
    mae = float(np.mean(np.abs(y - y_pred)))
    ss_res = float(np.sum((y - y_pred) ** 2))
    ss_tot = float(np.sum((y - np.mean(y)) ** 2))
    r2 = 1 - ss_res / ss_tot

    metrics = ["MSE", "RMSE", "MAE", "R²"]
    values = [round(mse, 3), round(rmse, 3), round(mae, 3), round(r2, 4)]
    colors = ["#6C63FF", "#a78bfa", "#FF6584", "#34d399"]

    data = [
        {
            "x": metrics,
            "y": values,
            "type": "bar",
            "marker": {
                "color": colors,
                "line": {"width": 1.5, "color": "#fff"},
            },
            "text": [str(v) for v in values],
            "textposition": "outside",
            "textfont": {"color": "#e0e0e0", "size": 14},
        }
    ]
    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Model Evaluation Metrics", "font": {"size": 18}},
        "xaxis": {"gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Value", "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 the full Linear Regression topic content."""
    return {
        "title": "Linear Regression",
        "icon": "📈",
        "subtitle": "The foundation of predictive modeling",
        "sections": [
            # ── 1. Introduction ──
            {
                "id": "introduction",
                "heading": "What is Linear Regression?",
                "type": "text",
                "content": (
                    "Linear Regression is one of the most fundamental and widely-used algorithms in machine learning and statistics. "
                    "It models the relationship between a <strong>dependent variable</strong> (target) and one or more <strong>independent variables</strong> (features) "
                    "by fitting a straight line (or hyperplane) through the data.\n\n"
                    "Despite its simplicity, linear regression is incredibly powerful — it forms the backbone of many real-world applications "
                    "from predicting house prices and stock trends to understanding scientific relationships."
                ),
            },
            # ── 2. Types ──
            {
                "id": "types",
                "heading": "Types of Linear Regression",
                "type": "text",
                "content": (
                    "<strong>Simple Linear Regression</strong> — models the relationship between a single feature and target. "
                    "The model equation is: <em>y = mx + b</em>, where <em>m</em> is the slope and <em>b</em> is the y-intercept.\n\n"
                    "<strong>Multiple Linear Regression</strong> — extends to multiple features. The model becomes: "
                    "<em>y = b₀ + b₁x₁ + b₂x₂ + ... + bₙxₙ</em>, where each <em>bᵢ</em> is the coefficient for feature <em>xᵢ</em>."
                ),
            },
            # ── 3. Intuition with Graph ──
            {
                "id": "intuition",
                "heading": "Visual Intuition — Fitting a Line",
                "type": "graph",
                "description": (
                    "The core idea is simple: find the line that best fits the data points. "
                    "Each blue dot is a data observation, and the red line is the best-fit linear model. "
                    "Hover over points to see their coordinates. Zoom and pan to explore the data."
                ),
                "graph_json": _scatter_with_regression(),
            },
            # ── 4. Mathematical Formulation ──
            {
                "id": "math",
                "heading": "The Mathematics",
                "type": "math",
                "content": [
                    {
                        "label": "Hypothesis Function",
                        "formula": "h_\\theta(x) = \\theta_0 + \\theta_1 x",
                        "explanation": "The predicted value is a linear combination of the input feature and model parameters (θ₀ = intercept, θ₁ = slope)."
                    },
                    {
                        "label": "Cost Function (Mean Squared Error)",
                        "formula": "J(\\theta) = \\frac{1}{2m} \\sum_{i=1}^{m} \\left( h_\\theta(x^{(i)}) - y^{(i)} \\right)^2",
                        "explanation": "The cost function measures the average squared difference between predictions and actual values. Our goal is to minimize this."
                    },
                    {
                        "label": "Gradient Descent Update",
                        "formula": "\\theta_j := \\theta_j - \\alpha \\frac{\\partial}{\\partial \\theta_j} J(\\theta)",
                        "explanation": "We iteratively update parameters in the direction that reduces the cost, with learning rate α controlling the step size."
                    },
                    {
                        "label": "Normal Equation (Closed-form)",
                        "formula": "\\theta = (X^T X)^{-1} X^T y",
                        "explanation": "For smaller datasets, we can compute the optimal parameters directly without iteration using the Normal Equation."
                    },
                ],
            },
            # ── 5. Cost Function Surface ──
            {
                "id": "cost-surface",
                "heading": "Cost Function Landscape",
                "type": "graph",
                "description": (
                    "This 3D surface shows how the Mean Squared Error (MSE) changes as we vary the slope and intercept. "
                    "The bowl shape means there's a single global minimum — the optimal parameters. "
                    "Drag to rotate, scroll to zoom."
                ),
                "graph_json": _cost_function_surface(),
            },
            # ── 6. Gradient Descent ──
            {
                "id": "gradient-descent",
                "heading": "Gradient Descent in Action",
                "type": "graph",
                "description": (
                    "Watch gradient descent navigate the cost landscape! The yellow star is the starting point, "
                    "and the green diamond is where it converges. The red path shows each step taken. "
                    "Notice how steps are larger at first (steep gradient) and smaller near the minimum."
                ),
                "graph_json": _gradient_descent_animation(),
            },
            # ── 7. Assumptions ──
            {
                "id": "assumptions",
                "heading": "Key Assumptions",
                "type": "list",
                "entries": [
                    {
                        "title": "Linearity",
                        "detail": "The relationship between features and target is linear. If this doesn't hold, consider polynomial features or non-linear models."
                    },
                    {
                        "title": "Independence",
                        "detail": "Observations are independent of each other. Violated in time-series data where consecutive observations are correlated."
                    },
                    {
                        "title": "Homoscedasticity",
                        "detail": "The variance of residuals is constant across all levels of the independent variables. Fan-shaped residual plots indicate violation."
                    },
                    {
                        "title": "Normality of Residuals",
                        "detail": "Residuals should be approximately normally distributed. Important for valid confidence intervals and hypothesis tests."
                    },
                    {
                        "title": "No Multicollinearity",
                        "detail": "Features should not be highly correlated with each other. Use VIF (Variance Inflation Factor) to detect this."
                    },
                ],
            },
            # ── 8. Residual Plot ──
            {
                "id": "residuals",
                "heading": "Residual Analysis",
                "type": "graph",
                "description": (
                    "A good model has residuals randomly scattered around zero with no patterns. "
                    "If you see a curve or funnel shape, the linear assumption may be violated. "
                    "The color intensity shows the magnitude and direction of each residual."
                ),
                "graph_json": _residual_plot(),
            },
            # ── 9. Python Code ──
            {
                "id": "implementation",
                "heading": "Python Implementation",
                "type": "code",
                "language": "python",
                "content": (
                    "import numpy as np\n"
                    "from sklearn.linear_model import LinearRegression\n"
                    "from sklearn.model_selection import train_test_split\n"
                    "from sklearn.metrics import mean_squared_error, r2_score\n"
                    "\n"
                    "# Generate sample data\n"
                    "np.random.seed(42)\n"
                    "X = np.random.rand(100, 1) * 10\n"
                    "y = 2.5 * X.squeeze() + 3 + np.random.randn(100) * 2\n"
                    "\n"
                    "# Train-test split\n"
                    "X_train, X_test, y_train, y_test = train_test_split(\n"
                    "    X, y, test_size=0.2, random_state=42\n"
                    ")\n"
                    "\n"
                    "# Fit the model\n"
                    "model = LinearRegression()\n"
                    "model.fit(X_train, y_train)\n"
                    "\n"
                    "# Predictions\n"
                    "y_pred = model.predict(X_test)\n"
                    "\n"
                    "# Evaluate\n"
                    "print(f'Slope (m):     {model.coef_[0]:.4f}')\n"
                    "print(f'Intercept (b): {model.intercept_:.4f}')\n"
                    "print(f'MSE:           {mean_squared_error(y_test, y_pred):.4f}')\n"
                    "print(f'R² Score:      {r2_score(y_test, y_pred):.4f}')\n"
                ),
            },
            # ── 10. Evaluation Metrics ──
            {
                "id": "metrics",
                "heading": "Evaluation Metrics",
                "type": "graph",
                "description": (
                    "These are the standard metrics used to evaluate a regression model. "
                    "<strong>R²</strong> (coefficient of determination) tells you what fraction of variance your model explains — "
                    "closer to 1.0 is better. <strong>MSE</strong>, <strong>RMSE</strong>, and <strong>MAE</strong> measure prediction error — lower is better."
                ),
                "graph_json": _metrics_bar_chart(),
            },
            # ── 11. Tips & Pitfalls ──
            {
                "id": "tips",
                "heading": "Common Pitfalls & Pro Tips",
                "type": "list",
                "entries": [
                    {
                        "title": "Feature Scaling",
                        "detail": "Gradient descent converges much faster when features are normalized (zero mean, unit variance). Use StandardScaler."
                    },
                    {
                        "title": "Overfitting with Many Features",
                        "detail": "Adding too many features can overfit. Use regularization (Ridge / Lasso) to penalize large coefficients."
                    },
                    {
                        "title": "Outliers Distort the Fit",
                        "detail": "Linear regression is sensitive to outliers. Always visualize your data first and consider robust regression if needed."
                    },
                    {
                        "title": "Don't Extrapolate",
                        "detail": "Predictions outside the range of your training data are unreliable. The linear relationship may not hold beyond observed values."
                    },
                    {
                        "title": "Check Residuals",
                        "detail": "Always plot residuals after fitting. Patterns in residuals indicate model mis-specification."
                    },
                ],
            },
        ],
    }
