"""
K-Means Clustering — comprehensive topic content with interactive graphs.
"""

import json
import numpy as np


def _clustering_demo():
    """Generate a K-Means clustering result visualization."""
    np.random.seed(42)
    centers = np.array([[0, 0], [4, 4], [-3, 5]])
    n_per = 60
    X = np.vstack([np.random.randn(n_per, 2) * 0.9 + c for c in centers])
    labels = np.repeat([0, 1, 2], n_per)
    colors_map = {0: "#6C63FF", 1: "#FF6584", 2: "#34d399"}

    data = []
    for k in range(3):
        mask = labels == k
        data.append({
            "x": X[mask, 0].tolist(), "y": X[mask, 1].tolist(),
            "mode": "markers", "type": "scatter", "name": f"Cluster {k}",
            "marker": {"size": 8, "color": colors_map[k], "opacity": 0.75, "line": {"width": 1, "color": "#fff"}},
        })
    data.append({
        "x": centers[:, 0].tolist(), "y": centers[:, 1].tolist(),
        "mode": "markers", "type": "scatter", "name": "Centroids",
        "marker": {"size": 18, "color": "#fbbf24", "symbol": "x", "line": {"width": 3, "color": "#fff"}},
    })

    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "K-Means Clustering (K=3)", "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 _elbow_method():
    """Generate an elbow plot for optimal K selection."""
    k_range = list(range(1, 11))
    inertias = [450, 200, 95, 55, 42, 38, 35, 33, 31.5, 30.5]

    data = [{
        "x": k_range, "y": inertias,
        "mode": "lines+markers", "type": "scatter", "name": "Inertia (WCSS)",
        "line": {"color": "#6C63FF", "width": 3},
        "marker": {"size": 8, "color": "#6C63FF", "line": {"width": 1, "color": "#fff"}},
    }, {
        "x": [3], "y": [95],
        "mode": "markers", "type": "scatter", "name": "Elbow Point (K=3)",
        "marker": {"size": 16, "color": "#FF6584", "symbol": "circle-open", "line": {"width": 3}},
    }]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Elbow Method for Optimal K", "font": {"size": 18}},
        "xaxis": {"title": "Number of Clusters (K)", "dtick": 1, "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Inertia (WCSS)", "gridcolor": "rgba(255,255,255,0.08)"},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def _silhouette_scores():
    """Show silhouette scores for different K values."""
    k_range = list(range(2, 11))
    scores = [0.45, 0.72, 0.58, 0.50, 0.43, 0.38, 0.35, 0.33, 0.30]

    data = [{"x": k_range, "y": scores, "type": "bar",
             "marker": {"color": ["#6C63FF" if s < 0.72 else "#34d399" for s in scores],
                        "line": {"width": 1.5, "color": "#fff"}},
             "text": [f"{s:.2f}" for s in scores], "textposition": "outside",
             "textfont": {"color": "#e0e0e0", "size": 13}}]
    layout = {
        "template": "plotly_dark", "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)",
        "title": {"text": "Silhouette Score by K", "font": {"size": 18}},
        "xaxis": {"title": "K", "dtick": 1, "gridcolor": "rgba(255,255,255,0.08)"},
        "yaxis": {"title": "Silhouette Score", "gridcolor": "rgba(255,255,255,0.08)", "range": [0, 0.85]},
        "margin": {"l": 60, "r": 30, "t": 50, "b": 50},
    }
    return json.dumps({"data": data, "layout": layout})


def get_content() -> dict:
    return {
        "title": "K-Means Clustering",
        "icon": "🔵",
        "subtitle": "Unsupervised grouping of data into K clusters",
        "sections": [
            {"id": "introduction", "heading": "What is K-Means?", "type": "text",
             "content": ("K-Means is the most popular <strong>unsupervised learning</strong> algorithm. It partitions "
                         "data into <strong>K clusters</strong> by iteratively assigning points to the nearest centroid "
                         "and then updating centroids to the mean of assigned points.\n\nNo labels needed — K-Means discovers "
                         "natural groupings in your data automatically.")},
            {"id": "clustering", "heading": "Clustering in Action", "type": "graph",
             "description": "Three clusters discovered by K-Means. The yellow × marks are the centroids. Each point is assigned to the cluster with the nearest centroid.",
             "graph_json": _clustering_demo()},
            {"id": "algorithm", "heading": "The Algorithm", "type": "list",
             "entries": [
                 {"title": "1. Initialize", "detail": "Randomly place K centroids (or use K-Means++ for smarter initialization)."},
                 {"title": "2. Assign", "detail": "Assign each point to the nearest centroid (using Euclidean distance)."},
                 {"title": "3. Update", "detail": "Recalculate each centroid as the mean of all its assigned points."},
                 {"title": "4. Repeat", "detail": "Repeat steps 2-3 until centroids stop moving (or max iterations reached)."},
             ]},
            {"id": "math", "heading": "The Mathematics", "type": "math",
             "content": [
                 {"label": "Objective (Inertia / WCSS)", "formula": "J = \\sum_{k=1}^{K} \\sum_{x_i \\in C_k} \\|x_i - \\mu_k\\|^2",
                  "explanation": "Minimize the sum of squared distances from each point to its cluster centroid. Also called Within-Cluster Sum of Squares."},
                 {"label": "Centroid Update", "formula": "\\mu_k = \\frac{1}{|C_k|} \\sum_{x_i \\in C_k} x_i",
                  "explanation": "The new centroid is simply the mean of all points assigned to cluster k."},
                 {"label": "Silhouette Score", "formula": "s(i) = \\frac{b(i) - a(i)}{\\max(a(i), b(i))}",
                  "explanation": "Measures cluster quality. a(i) = avg distance within cluster, b(i) = avg distance to nearest other cluster. Range: [-1, 1]."},
             ]},
            {"id": "elbow", "heading": "Choosing K — Elbow Method", "type": "graph",
             "description": "Plot inertia vs K. The 'elbow' point where adding more clusters gives diminishing returns suggests the optimal K.",
             "graph_json": _elbow_method()},
            {"id": "silhouette", "heading": "Choosing K — Silhouette Score", "type": "graph",
             "description": "The silhouette score measures how well-separated clusters are. Higher = better. K=3 gives the highest score here, confirming the elbow method.",
             "graph_json": _silhouette_scores()},
            {"id": "implementation", "heading": "Python Implementation", "type": "code", "language": "python",
             "content": ("from sklearn.cluster import KMeans\nfrom sklearn.preprocessing import StandardScaler\n"
                         "from sklearn.metrics import silhouette_score\nimport numpy as np\n\n"
                         "# Generate sample data\nnp.random.seed(42)\nX = np.vstack([\n"
                         "    np.random.randn(100, 2) * 0.9 + [0, 0],\n"
                         "    np.random.randn(100, 2) * 0.9 + [4, 4],\n"
                         "    np.random.randn(100, 2) * 0.9 + [-3, 5],\n])\n\n"
                         "# Scale features\nscaler = StandardScaler()\nX_scaled = scaler.fit_transform(X)\n\n"
                         "# Find optimal K\nfor k in range(2, 8):\n"
                         "    km = KMeans(n_clusters=k, n_init=10, random_state=42)\n"
                         "    labels = km.fit_predict(X_scaled)\n"
                         "    score = silhouette_score(X_scaled, labels)\n"
                         "    print(f'K={k}: Silhouette={score:.3f}, Inertia={km.inertia_:.1f}')\n\n"
                         "# Final model\nmodel = KMeans(n_clusters=3, n_init=10, random_state=42)\n"
                         "labels = model.fit_predict(X_scaled)\nprint(f'\\nCluster sizes: {np.bincount(labels)}')\n")},
            {"id": "tips", "heading": "Key Takeaways", "type": "list",
             "entries": [
                 {"title": "Always Scale", "detail": "K-Means uses Euclidean distance — features must be on the same scale."},
                 {"title": "Use K-Means++", "detail": "sklearn uses it by default (init='k-means++'). It spaces initial centroids apart for better convergence."},
                 {"title": "Run Multiple Times", "detail": "K-Means can converge to local minima. Set n_init=10+ to run with different initializations."},
                 {"title": "Not for All Shapes", "detail": "K-Means assumes spherical clusters. For non-convex shapes, use DBSCAN or Gaussian Mixture Models."},
                 {"title": "Sensitive to Outliers", "detail": "Outliers pull centroids. Consider removing them or using K-Medoids (PAM) instead."},
             ]},
        ],
    }
