"""Effect size de dos grupos: Cohen's d, Hedges' g e interpretacion cualitativa.

Funcion pura del grupo papers. El p-valor responde a "¿hay diferencia?" pero no
a "¿como de grande es?". El tamano del efecto (effect size) cuantifica la
magnitud de la diferencia entre dos grupos de forma adimensional, independiente
del N, y es lo que hace comparables resultados entre estudios (meta-analisis).

- Cohen's d: diferencia de medias estandarizada por la desviacion tipica
  combinada (pooled SD), con varianzas muestrales (ddof=1).
- Hedges' g: Cohen's d corregido por el sesgo al alza que sufre d con muestras
  pequenas, multiplicando por el factor de correccion J.
- interpretation: etiqueta cualitativa de |d| segun los umbrales clasicos de
  Cohen (negligible / small / medium / large).

No usa dependencias externas: aritmetica de la libreria estandar (``math``).
"""

from __future__ import annotations

import math


def _mean(xs: list) -> float:
    """Media aritmetica de una lista no vacia de numeros."""
    return sum(float(x) for x in xs) / len(xs)


def _sample_variance(xs: list, mean: float) -> float:
    """Varianza muestral (ddof=1) de una lista con al menos 2 elementos."""
    n = len(xs)
    return sum((float(x) - mean) ** 2 for x in xs) / (n - 1)


def _interpret(abs_d: float) -> str:
    """Etiqueta cualitativa del tamano del efecto segun |d| (umbrales de Cohen)."""
    if abs_d < 0.2:
        return "negligible"
    if abs_d < 0.5:
        return "small"
    if abs_d < 0.8:
        return "medium"
    return "large"


def effect_size_cohens_d(group_a: list, group_b: list) -> dict:
    """Calcula el tamano del efecto entre dos grupos numericos.

    Devuelve Cohen's d (diferencia de medias estandarizada por la pooled SD),
    Hedges' g (d corregido por sesgo de muestra pequena) y una etiqueta
    cualitativa de la magnitud segun los umbrales de Cohen.

    Es una funcion pura y determinista: no hace I/O, no muta la entrada. No lanza
    excepcion ante datos degenerados; en su lugar devuelve un dict con
    ``cohens_d`` / ``hedges_g`` a ``float('nan')``, ``interpretation`` a
    ``"undefined"`` y una clave ``note`` explicando el caso.

    Definiciones:
        s_pooled = sqrt(((n1-1)*s1^2 + (n2-1)*s2^2) / (n1+n2-2)), con s1^2, s2^2
            varianzas muestrales (ddof=1).
        cohens_d = (mean_a - mean_b) / s_pooled.
        J = 1 - 3 / (4*(n1+n2) - 9)  (factor de correccion de Hedges).
        hedges_g = cohens_d * J.

    Args:
        group_a: primera muestra (lista de numeros). Necesita >=2 elementos para
            que exista la varianza muestral.
        group_b: segunda muestra (lista de numeros). Necesita >=2 elementos.

    Returns:
        dict con las claves:
            cohens_d: float, diferencia de medias estandarizada (puede ser NaN).
            hedges_g: float, Cohen's d corregido por sesgo (puede ser NaN).
            interpretation: str, "negligible" | "small" | "medium" | "large", o
                "undefined" en casos degenerados.
            n_a: int, tamano de group_a.
            n_b: int, tamano de group_b.
            mean_a: float, media de group_a (NaN si vacio).
            mean_b: float, media de group_b (NaN si vacio).
            pooled_sd: float, desviacion tipica combinada (NaN si indefinida).

        Casos degenerados (lista vacia, N<2 en algun grupo, o varianza cero en
        ambos grupos -> pooled_sd == 0) anaden ademas una clave ``note``.
    """
    nan = float("nan")
    n_a = len(group_a)
    n_b = len(group_b)

    # Listas vacias: ni media ni varianza definidas.
    if n_a == 0 or n_b == 0:
        return {
            "cohens_d": nan,
            "hedges_g": nan,
            "interpretation": "undefined",
            "n_a": n_a,
            "n_b": n_b,
            "mean_a": _mean(group_a) if n_a else nan,
            "mean_b": _mean(group_b) if n_b else nan,
            "pooled_sd": nan,
            "note": "grupo vacio: media y varianza indefinidas, effect size indefinido",
        }

    mean_a = _mean(group_a)
    mean_b = _mean(group_b)

    # N insuficiente: la varianza muestral (ddof=1) no existe con un solo dato,
    # y la correccion de Hedges no es fiable.
    if n_a < 2 or n_b < 2:
        return {
            "cohens_d": nan,
            "hedges_g": nan,
            "interpretation": "undefined",
            "n_a": n_a,
            "n_b": n_b,
            "mean_a": mean_a,
            "mean_b": mean_b,
            "pooled_sd": nan,
            "note": (
                "N insuficiente: cada grupo necesita >=2 observaciones para la "
                "varianza muestral; effect size indefinido"
            ),
        }

    var_a = _sample_variance(group_a, mean_a)
    var_b = _sample_variance(group_b, mean_b)
    pooled_sd = math.sqrt(
        ((n_a - 1) * var_a + (n_b - 1) * var_b) / (n_a + n_b - 2)
    )

    # Varianza cero en ambos grupos: no se puede estandarizar (division por 0).
    if pooled_sd == 0.0:
        return {
            "cohens_d": nan,
            "hedges_g": nan,
            "interpretation": "undefined",
            "n_a": n_a,
            "n_b": n_b,
            "mean_a": mean_a,
            "mean_b": mean_b,
            "pooled_sd": 0.0,
            "note": "varianza cero, effect size indefinido",
        }

    cohens_d = (mean_a - mean_b) / pooled_sd
    j = 1.0 - 3.0 / (4.0 * (n_a + n_b) - 9.0)
    hedges_g = cohens_d * j

    return {
        "cohens_d": cohens_d,
        "hedges_g": hedges_g,
        "interpretation": _interpret(abs(cohens_d)),
        "n_a": n_a,
        "n_b": n_b,
        "mean_a": mean_a,
        "mean_b": mean_b,
        "pooled_sd": pooled_sd,
    }
