import numpy as np

def _skew_pop(v):
    v = np.asarray(v, dtype=np.float64)
    sd = v.std()
    if sd == 0:
        return 0.0
    return float(((v - v.mean()) ** 3).mean() / sd ** 3)

def kstar_rank(X, y, metric='euclidean', standardize=False):
    X = np.asarray(X, dtype=np.float64)
    y = np.asarray(y)
    if standardize:
        X = (X - X.mean(0, keepdims=True)) / (X.std(0, keepdims=True) + 1e-08)
    if metric == 'cosine':
        Xn = X / (np.linalg.norm(X, axis=1, keepdims=True) + 1e-12)
        D = 1.0 - Xn @ Xn.T
    elif metric in ('cityblock', 'l1'):
        D = np.abs(X[:, None, :] - X[None, :, :]).sum(-1)
    elif metric in ('chebyshev', 'linf'):
        D = np.abs(X[:, None, :] - X[None, :, :]).max(-1)
    else:
        sq = (X * X).sum(1)
        D = np.sqrt(np.maximum(sq[:, None] + sq[None, :] - 2 * X @ X.T, 0.0))
    np.fill_diagonal(D, np.inf)
    order = np.argsort(D, axis=1)
    diff = y[order] != y[:, None]
    kstar = diff.argmax(axis=1)
    has_diff = diff.any(axis=1)
    same_counts = (y[:, None] == y[None, :]).sum(1) - 1
    return np.where(has_diff, kstar, same_counts).astype(int)

def kstar_normalized(raw, y):
    y = np.asarray(y)
    out = np.empty(len(raw), dtype=np.float64)
    for c in np.unique(y):
        m = y == c
        out[m] = raw[m] / max(int(m.sum()), 1)
    return out

def typology_by_class(norm_kstar, y):
    y = np.asarray(y)
    res = {}
    for c in np.unique(y):
        v = norm_kstar[y == c]
        g = _skew_pop(v)
        label = 'Fractured' if g > 0.5 else 'Clustered' if g < -0.5 else 'Overlapped'
        res[int(c)] = {'label': label, 'skewness': g, 'mean': float(v.mean()), 'std': float(v.std()), 'n': int(len(v))}
    return res

def gamma_bootstrap_ci(norm_kstar, y, cls, n_boot=2000, seed=0):
    v = np.asarray(norm_kstar)[np.asarray(y) == cls]
    rng = np.random.default_rng(seed)
    n = len(v)
    boots = [_skew_pop(v[rng.integers(0, n, n)]) for _ in range(n_boot)]
    return (float(np.percentile(boots, 2.5)), float(np.percentile(boots, 97.5)))

def grc_spearman(norm_kstar_by_lang):
    from scipy.stats import spearmanr
    langs = list(norm_kstar_by_lang)
    vals, pairs = ([], [])
    for a in range(len(langs)):
        for b in range(a + 1, len(langs)):
            r, _ = spearmanr(norm_kstar_by_lang[langs[a]], norm_kstar_by_lang[langs[b]])
            r = 0.0 if np.isnan(r) else float(r)
            vals.append(r)
            pairs.append((langs[a], langs[b], round(r, 3)))
    return {'grc': float(np.mean(vals)) if vals else 0.0, 'pairs': pairs}
