"""kde_density_levels — Compute density levels via KDE or histogram fallback."""

import math
import numpy as np


def kde_density_levels(
    xs: list[float],
    ys: list[float],
    bw_adjust: float = 0.6,
    abs_quantile: float = 0.1,
    dense_quantile: float = 0.85,
    bins: int = 80,
) -> dict | None:
    """Estimate 2-D density and compute absolute and dense threshold levels.

    Uses scipy.stats.gaussian_kde when available; falls back to
    numpy.histogram2d if scipy is not installed.

    Args:
        xs: X-coordinates of points.
        ys: Y-coordinates of points.
        bw_adjust: Bandwidth adjustment factor for KDE (ignored for histogram fallback).
        abs_quantile: Quantile of density values used as the absolute threshold.
        dense_quantile: Quantile of density values used as the dense threshold.
        bins: Number of bins per axis for the histogram fallback.

    Returns:
        Dict with keys:
          "method"      (str):        "kde" or "hist".
          "densities"   (np.ndarray): 1-D array of per-point density estimates.
          "abs_level"   (float):      density at abs_quantile.
          "dense_level" (float):      density at dense_quantile.
        Returns None if len(xs) < 5 or xs and ys have different lengths.
    """
    if len(xs) < 5 or len(xs) != len(ys):
        return None

    xs_arr = np.array(xs, dtype=float)
    ys_arr = np.array(ys, dtype=float)
    points = np.vstack([xs_arr, ys_arr])

    try:
        from scipy.stats import gaussian_kde  # type: ignore

        kde = gaussian_kde(points, bw_method=bw_adjust)
        densities = kde(points)
        method = "kde"
    except ImportError:
        # Histogram fallback
        h, xedges, yedges = np.histogram2d(xs_arr, ys_arr, bins=bins)
        xi = np.clip(np.searchsorted(xedges, xs_arr) - 1, 0, bins - 1)
        yi = np.clip(np.searchsorted(yedges, ys_arr) - 1, 0, bins - 1)
        densities = h[xi, yi].astype(float)
        method = "hist"

    abs_level = float(np.quantile(densities, abs_quantile))
    dense_level = float(np.quantile(densities, dense_quantile))

    return {
        "method": method,
        "densities": densities,
        "abs_level": abs_level,
        "dense_level": dense_level,
    }
