"""
heat_transfer_correlations.py

Heat-transfer coefficient correlations for two-phase condensation.

Implemented
-----------
DengHTC
    Deng et al. (2019), Applied Thermal Engineering 153, 779–790.
    Mixture-condensation HTC with Bell–Ghaly correction and
    Deng flow-map (annular / stratified-wavy).
"""

import math
import numpy as np
from abc import ABC, abstractmethod
from .base_class_two_phase import TwoPhaseBase, g


class HeatTransferBase(TwoPhaseBase, ABC):
    """Enforces a uniform calc() interface for two-phase heat-transfer classes."""

    @abstractmethod
    def calc(self):
        """Return {'alpha': float, 'meta': dict}."""


class DengHTC(HeatTransferBase):
    """
    Condensation heat-transfer coefficient – Deng et al. (2019).

    Extends TwoPhaseBase with:
    - Log-mean void fraction (α) and Rouhani–Axelsson void fraction (α_ra)
    - Bell–Ghaly mixture correction
    - HTC for annular and stratified-wavy regimes

    Parameters
    ----------
    D       : float  – inner tube diameter [m]
    G       : float  – mass flux [kg/(m² s)]
    T_wall  : float  – wall temperature [K]
    CT_type : str    – "HC" (hydrocarbon) or other (affects J_G^T constant)
    state   : FluidState
    fluid   : carbatpy Fluid object
    """

    def __init__(self, D, G, T_wall, CT_type, state, fluid):
        super().__init__(state, fluid, D, G)
        self.T_wall = T_wall
        self.CT_type = CT_type
        self._calc_alpha()

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def calc(self):
        """
        Condensation HTC – Deng et al. (2019).

        Returns
        -------
        dict with keys: alpha [W/(m²K)], meta
        """
        self._determine_and_set_flow_field()
        self._calc_and_set_h_LO()
        self._calc_and_set_h_G()

        h_AM = self._calc_h_AM()
        if self.J_ratio <= 1:
            hSTRAT_mix = self._calc_h_stratM()
            h_m = h_AM * self.J_ratio ** 0.2 + hSTRAT_mix * (1 - self.J_ratio)
        else:
            h_m = h_AM

        if np.iscomplexobj(h_m) or np.isnan(h_m):
            raise ValueError("calc: complex or nan value detected")

        return {
            "alpha": float(h_m),
            "meta": {
                "x":       self.state.quality,
                "J_ratio": float(self.J_ratio),
                "h_LO":    float(self.h_LO),
                "h_G":     float(self.h_G),
                "h_A":     float(self.h_A),
                "R_A":     float(self.R_A),
            },
        }

    # ------------------------------------------------------------------
    # Flow regime
    # ------------------------------------------------------------------

    def _determine_and_set_flow_field(self):
        Xtt = self._calc_lockhart_martinelli_Xtt()
        J_G_T = self._calc_JG_T_transition(Xtt)
        J_G = self._calc_JG()
        self.J_ratio = J_G / J_G_T

    def _calc_lockhart_martinelli_Xtt(self):
        """
        X_tt = (μ_L/μ_G)^0.1 · (ρ_G/ρ_L)^0.5 · ((1−x)/x)^0.9
        Note: paper has a sign error – correct form uses ρ_G/ρ_L.
        """
        return ((self.state_l.viscosity / self.state_v.viscosity) ** 0.1
                * (self.state_l.sp_volume / self.state_v.sp_volume) ** 0.5
                * ((1.0 - self.state.quality) / self.state.quality) ** 0.9)

    def _calc_JG(self):
        return (self.state.quality * self.G
                / math.sqrt(g * self.D
                            * (self.state_l.sp_volume ** -1
                               - self.state_v.sp_volume ** -1)
                            * self.state_v.sp_volume ** -1))

    def _calc_JG_T_transition(self, Xtt):
        CT = 1.6 if self.CT_type == "HC" else 2.6
        return ((7.5 / (1.0 + 4.3 * Xtt ** 1.111)) ** -3 + CT ** -3) ** (-1 / 3)

    # ------------------------------------------------------------------
    # HTC sub-calculations
    # ------------------------------------------------------------------

    def _calc_and_set_h_LO(self):
        """Dittus–Boelter liquid-only: Nu = 0.023 Re^0.8 Pr^0.4."""
        Re = self.G * self.D / self.state_l.viscosity
        Pr_l = (self.state_l.viscosity * self.state_l.cp
                / self.state_l.thermal_conductivity)
        Nu = 0.023 * Re ** 0.8 * Pr_l ** 0.4
        self.h_LO = Nu * self.state_l.thermal_conductivity / self.D

    def _calc_and_set_h_G(self):
        """Dittus–Boelter gas-phase HTC for Bell–Ghaly resistance (Eq. 21)."""
        Re_g = (self.G * self.D * self.state.quality
                / (self.alpha * self.state_v.viscosity))  # Eq. (22)
        Pr_v = (self.state_v.viscosity * self.state_v.cp
                / self.state_v.thermal_conductivity)
        self.h_G = (0.023 * Re_g ** 0.8 * Pr_v ** (1 / 3)
                    * self.state_v.thermal_conductivity / self.D)

    def _calc_h_AM(self):
        """Eq. (25): h_{A,M} = 1 / (1/h_A + R_A)"""
        h_A = self._calc_hA()
        R_A = self._calc_RA_bell_ghaly()
        self.h_A = h_A
        self.R_A = R_A
        return 1.0 / (1.0 / h_A + R_A)

    def _calc_hA(self):
        """Eq. (13) / Eq. (15)."""
        Pr_l = (self.state_l.viscosity * self.state_l.cp
                / self.state_l.thermal_conductivity)
        a = (1 + 1.128 * self.state.quality ** 0.8170
             * (self.state_l.sp_volume ** -1
                / self.state_v.sp_volume ** -1) ** 0.3685
             * (self.state_l.viscosity
                / self.state_v.viscosity) ** 0.2363
             * (1 - self.state_v.viscosity
                / self.state_l.viscosity) ** 2.144
             * Pr_l ** -0.1)
        return self.h_LO * a

    def _calc_RA_bell_ghaly(self):
        """Eq. (26): R_A = R_A_raw / F_I"""
        return self._calc_RA() / self._calc_FI_interfacial_roughness()

    def _calc_RA(self):
        return ((self.state.quality * self.state_v.cp * self.dT_GL)
                / (self.dH_GL * self.h_G))

    def _calc_FI_interfacial_roughness(self):
        """Eq. (27)."""
        uG = self._calc_uG(self.alpha)
        uL = self._calc_uL(self.alpha)

        if self.J_ratio >= 1.0:
            theta_dry = 0.0
        else:
            theta_dry = self._biberg_theta_strat(self.alpha)
        delta = self._calc_film_thickness_deng(theta_dry)

        base = ((uG / uL) ** 0.5
                * ((self.state_l.sp_volume ** -1
                    - self.state_v.sp_volume ** -1)
                   * g * delta ** 2 / self.sigma) ** 0.25)
        if self.J_ratio < 1:
            base = base * self.J_ratio ** 0.2
        return 1 + base

    def _calc_uG(self, alpha_cs):
        return (self.G * self.state.quality
                / (self.state_v.sp_volume ** -1 * alpha_cs))

    def _calc_uL(self, alpha_cs):
        return (self.G * (1 - self.state.quality)
                / (self.state_l.sp_volume ** -1 * (1 - alpha_cs)))

    def _calc_h_stratM(self):
        """Eq. (28): h_{STRAT,M} = F_M · [1/(1/h_STRAT + R_STRAT)]"""
        h_STRAT = self._calc_h_strat()
        R_STRAT = self._calc_RA()
        F_M = self._calc_FM_non_equilibrium()
        h_STRAT_M = F_M * (1.0 / h_STRAT + R_STRAT) ** -1
        if np.iscomplexobj(h_STRAT_M) or np.isnan(h_STRAT_M):
            raise ValueError("_calc_h_stratM: complex or nan value detected")
        return h_STRAT_M

    def _calc_h_strat(self):
        num = (self.state_l.sp_volume ** -1
               * (self.state_l.sp_volume ** -1
                  - self.state_v.sp_volume ** -1)
               * g * self.state_l.thermal_conductivity ** 3 * self.dH_GL)
        den = (self.state_l.viscosity * self.D
               * (self.state.temperature - self.T_wall))
        a = 0.725 / (1 + 0.741
                     * ((1 - self.state.quality)
                        / self.state.quality) ** 0.3321)
        h_strat = (a * (num / den) ** 0.25
                   + (1 - self.state.quality ** 0.087) * self.h_LO)
        if np.iscomplexobj(h_strat) or np.isnan(h_strat):
            raise ValueError("_calc_h_strat: complex or nan value detected")
        return h_strat

    def _calc_FM_non_equilibrium(self):
        """Eq. (29): F_M = exp(−(1−J_G/J_G^T)² · ΔT_GL / (T − T_wall))"""
        return math.exp(-((1.0 - self.J_ratio) ** 2)
                        * self.dT_GL / (self.state.temperature - self.T_wall))

    def _calc_film_thickness_deng(self, theta_dry):
        """
        Film thickness δ = A_L / [R(2π − θ_dry)], A_L = (1−α_ra)·πD²/4.
        θ_dry = 0 for annular.
        """
        A_L = (1.0 - self.alpha_ra) * np.pi * self.D ** 2 / 4
        delta = A_L / max(0.5 * self.D * (2.0 * np.pi - theta_dry), 1e-30)
        return float(min(max(delta, 1e-9), 0.4999 * self.D))
