"""
Interactive probe – explore 3D semantic space with sliders.
"""

from __future__ import annotations

from typing import List

import numpy as np

from tracescope.models.analysis import AnalysisResult
from tracescope.analysis.explainer import SemanticExplainer


def probe_point(
    result: AnalysisResult,
    x: float,
    y: float,
    z: float,
    k_nearest: int = 5,
) -> dict:
    """Given a 3D coordinate, return nearest texts, cluster info, and axis values.

    Args:
        result: AnalysisResult from the pipeline.
        x, y, z: 3D coordinates of the probe.
        k_nearest: Number of nearest points to return.

    Returns:
        dict with nearest_texts, nearest_indices, cluster_distances,
        axis_percentages, etc.
    """
    pts = result.projected_3d
    probe = np.array([x, y, z])

    # Distances to all points
    dists = np.linalg.norm(pts - probe, axis=1)
    nearest_indices = np.argsort(dists)[:k_nearest]

    nearest_texts = [
        {
            "index": int(idx),
            "text": result.session.entries[idx].text,
            "role": result.session.entries[idx].role,
            "distance": float(dists[idx]),
            "cluster": result.clusters.labels[idx],
        }
        for idx in nearest_indices
    ]

    # Axis percentages (0-100%)
    mins = pts.min(axis=0)
    maxs = pts.max(axis=0)
    ranges = maxs - mins
    ranges[ranges == 0] = 1.0
    pct = ((probe - mins) / ranges) * 100

    # Cluster distances (closeness %)
    cluster_distances = {}
    for c in range(result.clusters.n_clusters):
        centroid = np.array(result.clusters.clusters[c]["centroid"][:3])
        dist = float(np.linalg.norm(probe - centroid))
        max_dist = float(np.linalg.norm(maxs - mins))
        closeness = max(0, (1 - dist / max_dist) * 100) if max_dist > 0 else 0
        label = (
            result.cluster_labels[c]
            if c < len(result.cluster_labels)
            else f"Cluster {c}"
        )
        cluster_distances[label] = round(closeness, 1)

    return {
        "probe": {"x": x, "y": y, "z": z},
        "axis_percentages": {
            result.axis_info.labels[i]: round(float(pct[i]), 1) for i in range(3)
        },
        "nearest_texts": nearest_texts,
        "cluster_distances": cluster_distances,
    }


def probe_with_explanation(
    result: AnalysisResult,
    explainer: SemanticExplainer,
    x: float,
    y: float,
    z: float,
    k_nearest: int = 5,
) -> dict:
    """Probe a point and get an LLM explanation of its meaning.

    Returns the same dict as probe_point plus an 'explanation' key.
    """
    info = probe_point(result, x, y, z, k_nearest)

    # Build cluster distances string
    dist_str = ", ".join(
        f"{name}: {pct}%" for name, pct in info["cluster_distances"].items()
    )

    nearest_texts = [item["text"] for item in info["nearest_texts"]]
    pcts = info["axis_percentages"]
    axis_labels = list(pcts.keys())
    axis_vals = list(pcts.values())

    explanation = explainer.explain_probe(
        axis_labels=axis_labels,
        x_val=axis_vals[0],
        y_val=axis_vals[1],
        z_val=axis_vals[2],
        nearest_texts=nearest_texts,
        cluster_distances=dist_str,
    )

    info["explanation"] = explanation
    return info
