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

import json
import numpy as np


def _sigmoid_curve():
    """Generate the sigmoid activation function curve."""
    x = np.linspace(-8, 8, 200)
    y = 1 / (1 + np.exp(-x))

    data = [
        {
            "x": x.tolist(),
            "y": y.tolist(),
            "mode": "lines",
            "type": "scatter",
            "name": "σ(z) = 1 / (1 + e⁻ᶻ)",
            "line": {"color": "#6C63FF", "width": 3},
        },
        {
            "x": [0], "y": [0.5],
            "mode": "markers",
            "type": "scatter",
            "name": "Decision Boundary (0.5)",
            "marker": {"size": 12, "color": "#FF6584", "symbol": "diamond"},
        },
        {
            "x": [-8, 8], "y": [0.5, 0.5],
            "mode": "lines",
            "type": "scatter",
            "name": "Threshold",
            "line": {"color": "#fbbf24", "width": 1.5, "dash": "dash"},
            "showlegend": False,
        },
    ]
    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Sigmoid Function", "font": {"size": 18}},
        "xaxis": {"title": "z (linear combination)", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "σ(z) — Probability", "gridcolor": "rgba(255,255,255,0.08)", "range": [-0.05, 1.05]},
        "legend": {"x": 0.02, "y": 0.98},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _classification_scatter():
    """Generate a 2D classification scatter with decision boundary."""
    np.random.seed(42)
    n = 80
    X0 = np.random.randn(n, 2) + np.array([-1.5, -1])
    X1 = np.random.randn(n, 2) + np.array([1.5, 1])

    data = [
        {
            "x": X0[:, 0].tolist(), "y": X0[:, 1].tolist(),
            "mode": "markers", "type": "scatter", "name": "Class 0",
            "marker": {"size": 9, "color": "#6C63FF", "opacity": 0.8, "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", "opacity": 0.8, "line": {"width": 1, "color": "#fff"}},
        },
        {
            "x": [-5, 5], "y": [5, -5],
            "mode": "lines", "type": "scatter", "name": "Decision Boundary",
            "line": {"color": "#fbbf24", "width": 2.5, "dash": "dash"},
        },
    ]
    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Binary Classification", "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]},
        "legend": {"x": 0.02, "y": 0.98},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _loss_comparison():
    """Compare log loss vs MSE for classification."""
    y_pred = np.linspace(0.01, 0.99, 200)
    log_loss = -np.log(y_pred)
    mse_loss = (1 - y_pred) ** 2

    data = [
        {
            "x": y_pred.tolist(), "y": log_loss.tolist(),
            "mode": "lines", "type": "scatter", "name": "Log Loss (Cross-Entropy)",
            "line": {"color": "#FF6584", "width": 3},
        },
        {
            "x": y_pred.tolist(), "y": mse_loss.tolist(),
            "mode": "lines", "type": "scatter", "name": "MSE Loss",
            "line": {"color": "#6C63FF", "width": 3},
        },
    ]
    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Loss Functions for y=1", "font": {"size": 18}},
        "xaxis": {"title": "Predicted Probability", "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Loss", "gridcolor": "rgba(255,255,255,0.08)", "range": [0, 5]},
        "legend": {"x": 0.55, "y": 0.98},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _confusion_matrix_chart():
    """Generate a confusion matrix heatmap."""
    cm = [[42, 8], [5, 45]]
    labels = ["Predicted 0", "Predicted 1"]
    actual = ["Actual 0", "Actual 1"]

    text = [["TN = 42", "FP = 8"], ["FN = 5", "TP = 45"]]

    data = [{
        "z": cm, "x": labels, "y": actual,
        "type": "heatmap",
        "colorscale": [[0, "#1a1a3e"], [0.5, "#6C63FF"], [1, "#FF6584"]],
        "text": text,
        "texttemplate": "%{text}",
        "textfont": {"size": 16, "color": "#fff"},
        "showscale": False,
    }]
    layout = {
        "template": "plotly_dark",
        "paper_bgcolor": "rgba(0,0,0,0)",
        "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Confusion Matrix", "font": {"size": 18}},
        "xaxis": {"side": "bottom"},
        "yaxis": {"autorange": "reversed"},
        "margin": {"l": 80, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def get_content() -> dict:
    return {
        "title": "Logistic Regression",
        "icon": "📊",
        "subtitle": "Classification through probability estimation",
        "sections": [
            {
                "id": "introduction",
                "heading": "What is Logistic Regression?",
                "type": "text",
                "content": (
                    "Despite its name, Logistic Regression is a <strong>classification</strong> algorithm, not a regression one. "
                    "It predicts the <strong>probability</strong> that a data point belongs to a particular class by passing a linear "
                    "combination of features through the <strong>sigmoid function</strong>.\n\n"
                    "It's the go-to algorithm for binary classification and serves as the building block for neural networks."
                ),
            },
            {
                "id": "sigmoid",
                "heading": "The Sigmoid Function",
                "type": "graph",
                "description": (
                    "The sigmoid function squashes any real number into the range [0, 1], making it perfect for "
                    "representing probabilities. When the output ≥ 0.5, we predict class 1; otherwise, class 0."
                ),
                "graph_json": _sigmoid_curve(),
            },
            {
                "id": "classification",
                "heading": "How Classification Works",
                "type": "graph",
                "description": (
                    "Logistic Regression finds a <strong>decision boundary</strong> — a line (or hyperplane) that separates "
                    "the two classes. Points on one side are predicted as class 0, and points on the other as class 1."
                ),
                "graph_json": _classification_scatter(),
            },
            {
                "id": "math",
                "heading": "The Mathematics",
                "type": "math",
                "content": [
                    {
                        "label": "Sigmoid (Logistic) Function",
                        "formula": "\\sigma(z) = \\frac{1}{1 + e^{-z}}",
                        "explanation": "Maps any input z to a probability between 0 and 1."
                    },
                    {
                        "label": "Hypothesis",
                        "formula": "h_\\theta(x) = \\sigma(\\theta^T x) = \\frac{1}{1 + e^{-\\theta^T x}}",
                        "explanation": "The predicted probability that the input belongs to class 1."
                    },
                    {
                        "label": "Binary Cross-Entropy Loss",
                        "formula": "J(\\theta) = -\\frac{1}{m} \\sum_{i=1}^{m} \\left[ y^{(i)} \\log(h_\\theta(x^{(i)})) + (1-y^{(i)}) \\log(1 - h_\\theta(x^{(i)})) \\right]",
                        "explanation": "Penalizes confident wrong predictions exponentially harder than uncertain ones."
                    },
                    {
                        "label": "Decision Rule",
                        "formula": "\\hat{y} = \\begin{cases} 1 & \\text{if } h_\\theta(x) \\geq 0.5 \\\\ 0 & \\text{otherwise} \\end{cases}",
                        "explanation": "The threshold can be adjusted depending on the problem (e.g., 0.3 for medical diagnosis to minimize false negatives)."
                    },
                ],
            },
            {
                "id": "loss",
                "heading": "Why Cross-Entropy, Not MSE?",
                "type": "graph",
                "description": (
                    "For classification, <strong>log loss</strong> (cross-entropy) penalizes wrong predictions much more "
                    "harshly than MSE. When the true label is 1 and the model predicts near 0, log loss skyrockets — "
                    "this creates a stronger gradient signal for learning."
                ),
                "graph_json": _loss_comparison(),
            },
            {
                "id": "confusion-matrix",
                "heading": "Confusion Matrix",
                "type": "graph",
                "description": (
                    "A confusion matrix shows the counts of True Positives (TP), True Negatives (TN), "
                    "False Positives (FP), and False Negatives (FN). From these, we derive precision, recall, "
                    "F1-score, and accuracy."
                ),
                "graph_json": _confusion_matrix_chart(),
            },
            {
                "id": "metrics-explained",
                "heading": "Evaluation Metrics",
                "type": "list",
                "entries": [
                    {"title": "Accuracy", "detail": "Overall correct predictions: (TP + TN) / Total. Can be misleading with imbalanced data."},
                    {"title": "Precision", "detail": "Of all positive predictions, how many were actually positive: TP / (TP + FP). Important when false positives are costly."},
                    {"title": "Recall (Sensitivity)", "detail": "Of all actual positives, how many did we catch: TP / (TP + FN). Critical in medical diagnosis."},
                    {"title": "F1-Score", "detail": "Harmonic mean of precision and recall: 2 × (Precision × Recall) / (Precision + Recall). Balances both metrics."},
                    {"title": "AUC-ROC", "detail": "Area Under the ROC Curve. Measures discrimination ability across all thresholds. 1.0 = perfect, 0.5 = random."},
                ],
            },
            {
                "id": "implementation",
                "heading": "Python Implementation",
                "type": "code",
                "language": "python",
                "content": (
                    "import numpy as np\n"
                    "from sklearn.linear_model import LogisticRegression\n"
                    "from sklearn.model_selection import train_test_split\n"
                    "from sklearn.metrics import classification_report, confusion_matrix\n"
                    "from sklearn.datasets import make_classification\n"
                    "\n"
                    "# Generate sample data\n"
                    "X, y = make_classification(\n"
                    "    n_samples=200, n_features=2, n_redundant=0,\n"
                    "    n_informative=2, random_state=42\n"
                    ")\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 = LogisticRegression()\n"
                    "model.fit(X_train, y_train)\n"
                    "\n"
                    "# Predictions\n"
                    "y_pred = model.predict(X_test)\n"
                    "\n"
                    "# Evaluate\n"
                    "print(classification_report(y_test, y_pred))\n"
                    "print('Confusion Matrix:')\n"
                    "print(confusion_matrix(y_test, y_pred))\n"
                ),
            },
            {
                "id": "types",
                "heading": "Variants & Extensions",
                "type": "list",
                "entries": [
                    {"title": "Multinomial Logistic Regression", "detail": "Extends to 3+ classes using the softmax function instead of sigmoid. Also known as Softmax Regression."},
                    {"title": "Regularized Logistic Regression", "detail": "Adds L1 (Lasso) or L2 (Ridge) penalty to prevent overfitting. Use C parameter in sklearn to control regularization strength."},
                    {"title": "One-vs-Rest (OvR)", "detail": "Trains K separate binary classifiers for K classes. Each classifier separates one class from all others."},
                ],
            },
            {
                "id": "tips",
                "heading": "Common Pitfalls & Pro Tips",
                "type": "list",
                "entries": [
                    {"title": "Feature Scaling Matters", "detail": "Standardize features before fitting — logistic regression uses gradient descent internally and converges faster with scaled input."},
                    {"title": "Class Imbalance", "detail": "Use class_weight='balanced' or resample the data when one class heavily outnumbers the other."},
                    {"title": "Linearity Assumption", "detail": "Assumes a linear relationship between features and the log-odds. Add polynomial or interaction terms if this doesn't hold."},
                    {"title": "Threshold Tuning", "detail": "Don't blindly use 0.5 as the threshold. Adjust based on the precision-recall tradeoff for your specific use case."},
                ],
            },
        ],
    }
