import pandas as pd
from typing import Dict, Union, Tuple

def get_optical_factors(
    optical_parameters: Dict[str, Dict[str, float]],
    aerosol: str
    ) -> Tuple[float, float, float, float, float]:
    """
    calculate the common factors used in optical properties calculations.

    Args:
        optical_parameters (dict): Optical parameters of the aerosol.
        aerosol (str): Name of the aerosol.

    Returns:
        Tuple[float, float, float, float, float]: A tuple containing:
            - qext: Extinction efficiency factor.
            - qsca: Scattering efficiency factor.
            - vphi: Volume fraction.
            - dens: Density [g/cm³].
            - reff: Effective radius [um].
    """
    qext = optical_parameters[aerosol.lower()]['qext']
    qsca = optical_parameters[aerosol.lower()]['qsca']
    vphi = optical_parameters[aerosol.lower()]['vphi']
    dens = optical_parameters[aerosol.lower()]['dens']
    reff = optical_parameters[aerosol.lower()]['reff']

    return qext, qsca, vphi, dens, reff


def total_absorption_in_layer(
    model_conc: float,
    optical_parameters: Dict[str, Dict[str, float]],
    thickness_layer: float,
    aerosol: str
    ) -> float:
    """
    Calculate the total absorption of an aerosol layer.

    Args:
        model_conc (float): Concentration of aerosol layer [ug/m³].
        optical_parameters (dict): Optical parameters of the aerosol.
        thickness_layer (float): Thickness of aerosol layer [m].
        aerosol (str): Name of the aerosol.

    Returns:
        float: Total absorption in the layer.
    """

    qext, qsca, vphi, dens, reff = get_optical_factors(optical_parameters, aerosol)

    model_conc_g_m3 = model_conc * 1e-6  # convert ug/m³ to g/m³

    total_absorption = ((3 * (qext - qsca) * vphi) /
                        (4 * dens * reff)) * model_conc_g_m3 * thickness_layer

    return total_absorption


def absorption_coefficient(
    model_conc: float,
    optical_parameters: Dict[str, Dict[str, float]],
    aerosol: str
    ) -> float:
    """
    Calculate the absorption coefficient of an aerosol layer.

    Args:
        model_conc (float): Concentration of aerosol layer [ug/m³].
        optical_parameters (dict): Optical parameters of the aerosol.
        aerosol (str): Name of the aerosol.

    Returns:
        float: Absorption coefficient in [Mm⁻¹].
    """

    qext, qsca, vphi, dens, reff = get_optical_factors(optical_parameters, aerosol)

    model_conc_g_m3 = model_conc * 1e-6  # convert ug/m³ to g/m³

    abs_coeff = ((3 * (qext - qsca) * vphi) /
                 (4 * dens * reff)) * model_conc_g_m3

    return abs_coeff * 1e6  # convert to Mm⁻¹

def aerosol_optical_depth(
    model_conc: float,
    optical_parameters: Dict[str, Dict[str, float]],
    thickness_layer: float,
    aerosol: str
    ) -> float:
    """
    Calculate the aerosol optical depth of an aerosol layer.

    Args:
        model_conc (float): Concentration of aerosol layer [ug/m³].
        optical_parameters (dict): Optical parameters of the aerosol.
        thickness_layer (float): Thickness of aerosol layer [m].
        aerosol (str): Name of the aerosol.

    Returns:
        float: Aerosol optical depth.
    """

    qext, _, vphi, dens, reff = get_optical_factors(optical_parameters, aerosol)

    model_conc_g_m3 = model_conc * 1e-6  # convert ug/m³ to g/m³

    aod = ((3 * (qext * vphi) /
            (4 * dens * reff)) * model_conc_g_m3 * thickness_layer)

    return aod
