"""
K-Nearest Neighbors (KNN) — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _knn_visualization():
    """Show KNN classification with decision regions."""
    np.random.seed(42)
    n = 60
    X0 = np.random.randn(n, 2) * 0.9 + np.array([-1.5, 0])
    X1 = np.random.randn(n, 2) * 0.9 + np.array([1.5, 0])
    X2 = np.random.randn(n, 2) * 0.9 + np.array([0, 2])
    query = [0.5, 1.0]

    # Find 5 nearest neighbors
    all_X = np.vstack([X0, X1, X2])
    dists = np.sqrt(np.sum((all_X - query) ** 2, axis=1))
    nn_idx = np.argsort(dists)[:5]

    data = [
        {"x": X0[:, 0].tolist(), "y": X0[:, 1].tolist(), "mode": "markers", "type": "scatter", "name": "Class A",
         "marker": {"size": 8, "color": "#6C63FF", "opacity": 0.7, "line": {"width": 1, "color": "#fff"}}},
        {"x": X1[:, 0].tolist(), "y": X1[:, 1].tolist(), "mode": "markers", "type": "scatter", "name": "Class B",
         "marker": {"size": 8, "color": "#FF6584", "opacity": 0.7, "line": {"width": 1, "color": "#fff"}}},
        {"x": X2[:, 0].tolist(), "y": X2[:, 1].tolist(), "mode": "markers", "type": "scatter", "name": "Class C",
         "marker": {"size": 8, "color": "#34d399", "opacity": 0.7, "line": {"width": 1, "color": "#fff"}}},
        {"x": [query[0]], "y": [query[1]], "mode": "markers", "type": "scatter", "name": "Query Point (? )",
         "marker": {"size": 16, "color": "#fbbf24", "symbol": "star", "line": {"width": 2, "color": "#fff"}}},
        {"x": all_X[nn_idx, 0].tolist(), "y": all_X[nn_idx, 1].tolist(), "mode": "markers", "type": "scatter", "name": "5 Nearest Neighbors",
         "marker": {"size": 14, "color": "rgba(0,0,0,0)", "line": {"width": 3, "color": "#fbbf24"}}},
    ]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "KNN Classification (K=5)", "font": {"size": 18}},
        "xaxis": {"title": "Feature 1", "gridcolor": "rgba(255,255,255,0.08)"},
        "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 _k_vs_accuracy():
    """Show effect of K on accuracy."""
    k_values = list(range(1, 31))
    np.random.seed(42)
    train_acc = [1.0 / (1 + 0.01 * k) + 0.02 * np.random.randn() for k in k_values]
    val_acc = [0.65 + 0.28 * (1 - np.exp(-0.3 * k)) - 0.012 * k + 0.02 * np.random.randn() for k in k_values]

    data = [
        {"x": k_values, "y": train_acc, "mode": "lines+markers", "type": "scatter", "name": "Training Accuracy",
         "line": {"color": "#34d399", "width": 2.5}, "marker": {"size": 4}},
        {"x": k_values, "y": val_acc, "mode": "lines+markers", "type": "scatter", "name": "Validation Accuracy",
         "line": {"color": "#FF6584", "width": 2.5}, "marker": {"size": 4}},
        {"x": [7, 7], "y": [0.5, 1.05], "mode": "lines", "type": "scatter", "name": "Optimal K",
         "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": "Effect of K on Accuracy", "font": {"size": 18}},
        "xaxis": {"title": "K (Number of Neighbors)", "gridcolor": "rgba(255,255,255,0.08)", "dtick": 5},
        "yaxis": {"title": "Accuracy", "gridcolor": "rgba(255,255,255,0.08)", "range": [0.55, 1.05]},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _distance_metrics():
    """Compare distance metrics visually."""
    metrics = ["Euclidean", "Manhattan", "Minkowski (p=3)", "Cosine"]
    values = [2.83, 4.0, 2.52, 0.18]
    colors = ["#6C63FF", "#FF6584", "#a78bfa", "#34d399"]

    data = [{"x": metrics, "y": values, "type": "bar",
             "marker": {"color": colors, "line": {"width": 1.5, "color": "#fff"}},
             "text": [f"{v:.2f}" 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": "Distance Metrics for Points (1,2) → (3,4)", "font": {"size": 18}},
        "yaxis": {"title": "Distance 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 {
        "title": "K-Nearest Neighbors",
        "icon": "📍",
        "subtitle": "Classify by finding the closest neighbors",
        "sections": [
            {"id": "introduction", "heading": "What is KNN?", "type": "text",
             "content": ("K-Nearest Neighbors is one of the simplest ML algorithms — it makes predictions by finding "
                         "the <strong>K closest data points</strong> in the training set and using their labels to vote.\n\n"
                         "It's a <strong>lazy learner</strong>: no model is trained. The entire training set is stored, "
                         "and prediction happens at query time by distance computation.")},
            {"id": "visualization", "heading": "How KNN Works", "type": "graph",
             "description": "The yellow star is the query point. We find its 5 nearest neighbors (circled) and take a majority vote of their classes to make the prediction.",
             "graph_json": _knn_visualization()},
            {"id": "k-effect", "heading": "Choosing K", "type": "graph",
             "description": "K=1 memorizes training data (overfitting). Large K smooths boundaries (underfitting). Use cross-validation to find the sweet spot.",
             "graph_json": _k_vs_accuracy()},
            {"id": "math", "heading": "The Mathematics", "type": "math",
             "content": [
                 {"label": "Euclidean Distance", "formula": "d(x, y) = \\sqrt{\\sum_{i=1}^{n} (x_i - y_i)^2}",
                  "explanation": "The most common distance metric. Works well when features are on similar scales."},
                 {"label": "Manhattan Distance", "formula": "d(x, y) = \\sum_{i=1}^{n} |x_i - y_i|",
                  "explanation": "Sum of absolute differences. More robust to outliers. Better for high-dimensional sparse data."},
                 {"label": "Weighted KNN", "formula": "\\hat{y} = \\arg\\max_c \\sum_{i \\in N_k(x)} \\frac{1}{d(x, x_i)} \\cdot \\mathbb{1}(y_i = c)",
                  "explanation": "Closer neighbors get more vote weight (inverse distance). Reduces sensitivity to K."},
             ]},
            {"id": "distances", "heading": "Distance Metrics Compared", "type": "graph",
             "description": "Different metrics compute different notions of 'closeness'. The choice of metric can significantly affect results, especially in high dimensions.",
             "graph_json": _distance_metrics()},
            {"id": "implementation", "heading": "Python Implementation", "type": "code", "language": "python",
             "content": ("from sklearn.neighbors import KNeighborsClassifier\n"
                         "from sklearn.preprocessing import StandardScaler\n"
                         "from sklearn.model_selection import train_test_split, cross_val_score\n"
                         "from sklearn.datasets import load_iris\nimport numpy as np\n\n"
                         "# Load and scale data\niris = load_iris()\nX, y = iris.data, iris.target\n"
                         "scaler = StandardScaler()\nX_scaled = scaler.fit_transform(X)\n\n"
                         "# Find optimal K via cross-validation\nk_range = range(1, 21)\n"
                         "scores = [cross_val_score(KNeighborsClassifier(k), X_scaled, y, cv=5).mean() for k in k_range]\n"
                         "best_k = k_range[np.argmax(scores)]\nprint(f'Best K: {best_k} (CV Accuracy: {max(scores):.4f})')\n\n"
                         "# Final model\nX_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)\n"
                         "model = KNeighborsClassifier(n_neighbors=best_k, weights='distance')\n"
                         "model.fit(X_train, y_train)\nprint(f'Test Accuracy: {model.score(X_test, y_test):.4f}')\n")},
            {"id": "tips", "heading": "Key Takeaways", "type": "list",
             "entries": [
                 {"title": "Always Scale Features", "detail": "KNN uses distances — features with larger ranges will dominate. Always standardize."},
                 {"title": "Curse of Dimensionality", "detail": "KNN degrades in high dimensions because distances become meaningless. Use PCA or feature selection first."},
                 {"title": "Use Odd K for Binary", "detail": "Avoids ties in voting. For multi-class, implement a tiebreaking strategy."},
                 {"title": "Slow at Prediction Time", "detail": "Must compute distance to ALL training points. Use KD-Trees or Ball Trees for speedup."},
                 {"title": "Weighted Voting Helps", "detail": "Set weights='distance' to give nearer neighbors more influence. Often improves accuracy."},
             ]},
        ],
    }
