import numpy as np
from sklearn.mixture import GaussianMixture
from scipy.fft import fft
from scipy.stats import gaussian_kde

# Gaussian Mixture Model-based relevance
def phi_gmm(y, n_components=3):
    y = np.array(y).reshape(-1, 1)
    gmm = GaussianMixture(n_components=n_components, random_state=0).fit(y)
    probs = np.exp(gmm.score_samples(y))
    rel = 1 - (probs - probs.min()) / (probs.max() - probs.min())
    return rel.tolist()

# Kernel Density Estimation-based relevance
def phi_kde(y, bandwidth=0.2):
    y = np.array(y)
    kde = gaussian_kde(y, bw_method=bandwidth)
    probs = kde.evaluate(y)
    rel = 1 - (probs - probs.min()) / (probs.max() - probs.min())
    return rel.tolist()

# Histogram-based relevance

def phi_hist(y, bins=20):
    y = np.array(y)
    counts, bin_edges = np.histogram(y, bins=bins)

    # Compute phi relevance per bin (higher count = lower relevance)
    denom = counts.max() - counts.min()
    if denom == 0:
        rel = np.zeros_like(counts, dtype=float)
    else:
        rel = 1 - (counts - counts.min()) / denom

    bin_centers = 0.5 * (bin_edges[1:] + bin_edges[:-1])
    
    # For each y value, find corresponding bin index and assign phi
    bin_indices = np.digitize(y, bin_edges[:-1], right=True)
    bin_indices = np.clip(bin_indices, 0, len(counts) - 1)
    y_phi = rel[bin_indices]

    return bin_centers.tolist(), rel.tolist(), y_phi.tolist()

# Spectral Density Estimation-based relevance (unusual for regression, heuristic)
def phi_spectral(y):
    y = np.array(y)
    n = len(y)
    y_centered = y - np.mean(y)

    # Get power spectrum
    spectrum = np.abs(fft(y_centered))**2
    spectrum = spectrum[:n // 2]  # keep only positive frequencies

    # Normalize energy per point based on cumulative FFT magnitude
    spectral_energy = np.cumsum(spectrum)
    spectral_energy = spectral_energy / spectral_energy.max()

    # Map each original y to its frequency influence (crude, for 1D)
    # Use rank-based rarity: values where signal rapidly changes = high-frequency
    rank = np.argsort(np.argsort(np.abs(y_centered)))
    rel_score = rank / rank.max()  # higher = rarer

    return rel_score.tolist()

# Dispatcher for all phi methods
def phi_custom(y, method="gmm"):
    if method == "gmm":
        return phi_gmm(y)
    elif method == "kde":
        return phi_kde(y)
    elif method == "hist":
        return phi_hist(y)
    elif method == "spectral":
        return phi_spectral(y)
    else:
        raise ValueError("Unknown method: choose from 'gmm', 'kde', 'hist', 'spectral'")

