"""Pharmacodynamic models for anesthesia depth monitoring.

Implements the Hill sigmoid Emax model for BIS (Bispectral Index) prediction
from effect-site drug concentrations, and the Greco interaction model for
combined propofol-remifentanil effects.

References:
    Schnider, T. W., Minto, C. F., Shafer, S. L., et al. (1999).
    The influence of age on propofol pharmacodynamics. Anesthesiology,
    90(6), 1502-1516.

    Bouillon, T. W., Bruhn, J., Radulescu, L., et al. (2004).
    Pharmacodynamic interaction between propofol and remifentanil
    regarding hypnosis, tolerance of laryngoscopy, bispectral index,
    and electroencephalographic approximate entropy. Anesthesiology,
    100(6), 1353-1372.

    Greco, W. R., Bravo, G., & Parsons, J. C. (1995). The search for
    synergy: a critical review from a response surface perspective.
    Pharmacological Reviews, 47(2), 331-385.
"""

from __future__ import annotations


# SIMPLIFICATION: Using population mean Ce50 and gamma values.
# Clinical systems use patient-specific estimates from Bayesian
# updating. Population means are standard for benchmark work.

PARAMETER_RANGES = {
    "BIS_0": {
        "min": 93, "max": 98, "unit": "dimensionless",
        "source": "Schnider et al., 1999", "default": 97,
    },
    "Ce50_propofol": {
        "min": 2.5, "max": 5.0, "unit": "mcg/mL",
        "source": "Schnider et al., 1999", "default": 3.4,
    },
    "gamma_propofol": {
        "min": 1.5, "max": 4.5, "unit": "dimensionless",
        "source": "Schnider et al., 1999", "default": 3.0,
    },
    "Ce50_remifentanil": {
        "min": 10.0, "max": 30.0, "unit": "ng/mL",
        "source": "Bouillon et al., 2004", "default": 19.9,
    },
    "alpha_interaction": {
        "min": 0.5, "max": 4.0, "unit": "dimensionless",
        "source": "Bouillon et al., 2004", "default": 1.96,
    },
}


class HillPDModel:
    """Sigmoid Emax (Hill) model for propofol-BIS relationship.

    BIS = BIS_0 * (1 - E_max * Ce^gamma / (Ce50^gamma + Ce^gamma))

    Maps effect-site propofol concentration to predicted BIS value.
    """

    def __init__(
        self,
        BIS_0: float = 97.0,
        E_max: float = 1.0,
        Ce50: float = 3.4,
        gamma: float = 3.0,
    ) -> None:
        self.BIS_0 = BIS_0
        self.E_max = E_max
        self.Ce50 = Ce50
        self.gamma = gamma

    def predict_BIS(self, Ce: float) -> float:
        """Predict BIS from effect-site concentration.

        Args:
            Ce: Effect-site propofol concentration (mcg/mL).

        Returns:
            Predicted BIS value (0-97 range).
        """
        if Ce <= 0.0:
            return self.BIS_0
        Ce_g = Ce ** self.gamma
        Ce50_g = self.Ce50 ** self.gamma
        effect = self.E_max * Ce_g / (Ce50_g + Ce_g)
        return max(0.0, self.BIS_0 * (1.0 - effect))

    def add_noise(self, bis: float, rng, noise_std: float = 2.0) -> float:
        """Add measurement noise to BIS value (simulating sensor noise).

        Args:
            bis: Clean BIS value.
            rng: numpy random generator.
            noise_std: Standard deviation of Gaussian noise.

        Returns:
            Noisy BIS value, clamped to [0, 100].
        """
        noisy = bis + rng.normal(0.0, noise_std)
        return float(max(0.0, min(100.0, noisy)))


class GrecoInteractionModel:
    """Greco response surface model for propofol-remifentanil interaction.

    Models the synergistic interaction between propofol and remifentanil
    on BIS using a normalized drug potency surface.

    U = (Ce_prop/Ce50_prop) + (Ce_remi/Ce50_remi)
        + alpha * (Ce_prop/Ce50_prop) * (Ce_remi/Ce50_remi)

    BIS = BIS_0 * (1 - U^gamma / (1 + U^gamma))

    The alpha parameter quantifies synergy:
        alpha > 0: synergistic interaction (less total drug needed)
        alpha = 0: additive interaction (Loewe additivity)
        alpha < 0: antagonistic interaction
    """

    def __init__(
        self,
        BIS_0: float = 97.0,
        Ce50_propofol: float = 3.4,
        Ce50_remifentanil: float = 19.9,
        alpha: float = 1.96,
        gamma: float = 1.43,
    ) -> None:
        self.BIS_0 = BIS_0
        self.Ce50_propofol = Ce50_propofol
        self.Ce50_remifentanil = Ce50_remifentanil
        self.alpha = alpha
        self.gamma = gamma

    def predict_BIS(self, Ce_propofol: float, Ce_remifentanil: float) -> float:
        """Predict BIS from combined effect-site concentrations.

        Args:
            Ce_propofol: Propofol effect-site concentration (mcg/mL).
            Ce_remifentanil: Remifentanil effect-site concentration (ng/mL).

        Returns:
            Predicted BIS value.
        """
        u_prop = Ce_propofol / self.Ce50_propofol if self.Ce50_propofol > 0 else 0.0
        u_remi = Ce_remifentanil / self.Ce50_remifentanil if self.Ce50_remifentanil > 0 else 0.0

        U = u_prop + u_remi + self.alpha * u_prop * u_remi
        U = max(0.0, U)

        U_g = U ** self.gamma
        effect = U_g / (1.0 + U_g)
        return max(0.0, self.BIS_0 * (1.0 - effect))

    def add_noise(self, bis: float, rng, noise_std: float = 2.0) -> float:
        """Add measurement noise to BIS."""
        noisy = bis + rng.normal(0.0, noise_std)
        return float(max(0.0, min(100.0, noisy)))
