import numpy as np
import numpy.typing as npt

from pixelemon.constants import MAD_TO_STD


def get_median_absolute_deviation(data: npt.NDArray[np.float32]) -> float:
    median = np.median(data)
    return float(np.median(np.abs(data - median)))


def get_standard_deviation(data: npt.NDArray[np.float32], use_mad: bool) -> float:
    if use_mad:
        mad = get_median_absolute_deviation(data)
        return mad * MAD_TO_STD
    else:
        return float(np.std(data))


def map_within_std_threshold(dat: npt.NDArray[np.float32], lim: float, use_mad: bool) -> npt.NDArray[np.bool_]:
    """Returns a boolean mask where True indicates the value is within the specified standard deviation threshold."""
    lower, upper = get_std_threshold_bounds(dat, lim, use_mad)
    return (dat >= lower) & (dat <= upper)


def map_outside_std_threshold(dat: npt.NDArray[np.float32], lim: float, use_mad: bool) -> npt.NDArray[np.bool_]:
    """Returns a boolean mask where True indicates the value is outside the specified standard deviation threshold."""
    lower, upper = get_std_threshold_bounds(dat, lim, use_mad)
    return (dat < lower) | (dat > upper)


def get_std_threshold_bounds(dat: npt.NDArray[np.float32], lim: float, use_mad: bool) -> tuple[float, float]:
    """Returns the lower and upper bounds based on the specified standard deviation threshold."""
    std = get_standard_deviation(dat, use_mad)
    if use_mad:
        center = np.median(dat)
    else:
        center = np.mean(dat)
    lower_bound = center - lim * std
    upper_bound = center + lim * std
    return float(lower_bound), float(upper_bound)


def map_outside_value_threshold(dat: npt.NDArray[np.float32], min_val: float, max_val: float) -> npt.NDArray[np.bool_]:
    """Returns a boolean mask where True indicates the value is outside the specified value thresholds."""
    return (dat < min_val) | (dat > max_val)
