# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/utils/convert.ipynb.

# %% auto 0
__all__ = ['print_md', 'convert_epoch_millisecond_to_datetime', 'convert_datetime_to_epoch_millisecond',
           'convert_string_to_datetime', 'convert_python_to_ast_module', 'extract_ast_functions',
           'convert_programming_text_to_title_case', 'convert_snake_to_pascal', 'convert_str_to_snake_case',
           'InvalidEmail', 'test_valid_email', 'convert_string_to_bool', 'ConcatDataframe_InvalidElement',
           'concat_list_dataframe', 'merge_dict']

# %% ../../nbs/utils/convert.ipynb 2
import datetime as dt
from dateutil import parser
import pandas as pd
import re
from typing import List
import ast
from IPython.display import display_markdown

# %% ../../nbs/utils/convert.ipynb 5
def print_md(md_str):
    display_markdown(md_str, raw = True)

# %% ../../nbs/utils/convert.ipynb 7
def convert_epoch_millisecond_to_datetime(epoch: int):
    """convert Epoch time with miliseconds to Date time"""
    return dt.datetime.fromtimestamp(epoch / 1000.0) if epoch else None

# %% ../../nbs/utils/convert.ipynb 8
def convert_datetime_to_epoch_millisecond(datetime: dt.datetime):
    """convert DateTime to Epoch time with Miliseconds"""
    return int(datetime.timestamp() * 1000) if datetime else None

# %% ../../nbs/utils/convert.ipynb 11
def convert_string_to_datetime(datestr: str) -> dt.datetime:
    if not datestr:
        return None
    return parser.parse(datestr)

# %% ../../nbs/utils/convert.ipynb 13
def convert_python_to_ast_module(
    python_str: str = None, python_file_path: str = None, return_str: bool = False
) -> ast.Module:
    """
    Parses a Python code string and returns its AST module.
    """

    if not python_str and python_file_path:

        with open(python_file_path, "r") as source:
            python_str = source.read()

    if not python_str:
        raise ValueError("must pass a python_str or python_file_path")
    
    if return_str:
        return python_str
    
    return ast.parse(python_str)



def extract_ast_functions(ast_module: ast.Module) -> List[ast.FunctionDef]:
    """
    Extracts all function definitions from an AST module.
    """
    return [node for node in ast_module.body if isinstance(node, ast.FunctionDef)]

# %% ../../nbs/utils/convert.ipynb 15
def convert_programming_text_to_title_case(clean_str: str 
                                           ) -> str:
    """
    Convert function names from programming conventions to human-readable display format.

    Transforms snake_case and camelCase function names into Title Case format suitable
    for user interfaces. Preserves leading underscores as spaces to maintain private
    function indicators.

    Args:
        clean_str (str): The original function name in snake_case or camelCase

    Returns:
        str: The formatted display name in Title Case

    Examples:
        >>> convert_programming_text_to_title_case('getUserData')
        'Get User Data'
        >>> convert_programming_text_to_title_case('calculate_total_sum')
        'Calculate Total Sum'
        >>> convert_programming_text_to_title_case('_private_method')
        ' Private Method'
    """

    leading_underscores = ""
    clean_str = clean_str
    
    while clean_str.startswith("_"):
        leading_underscores += " "
        clean_str = clean_str[1:]

    spaced_name = re.sub(r"([a-z])([A-Z])", r"\1 \2", clean_str)
    spaced_name = spaced_name.replace("_", " ")

    return leading_underscores + " ".join(
        word.capitalize() for word in spaced_name.split()
    )

# %% ../../nbs/utils/convert.ipynb 17
def convert_snake_to_pascal(clean_str):
    """converts 'snake_case_str' to 'snakeCaseStr'"""

    clean_str = clean_str.replace("_", " ").title().replace(" ", "")
    return clean_str[0].lower() + clean_str[1:]

# %% ../../nbs/utils/convert.ipynb 20
def convert_str_to_snake_case(text_str, 
                              is_only_alphanumeric: bool = False,
                              is_pascal : bool = False
                              ):
    """converts 'snake_case_str' to 'snakeCaseStr'"""

    if is_pascal:
        text_str = re.sub(r'(?<!^)(?=[A-Z])', '_', text_str)
    
    text_str = text_str.replace(" ", "_").lower()

    if is_only_alphanumeric:
        text_str = re.sub(r"\W+", "", text_str)


    return text_str

# %% ../../nbs/utils/convert.ipynb 23
class InvalidEmail(Exception):
    def __init__(self, email):

        super().__init__(f'invalid email: "{email}" provided')


def test_valid_email(email):
    """tests if provided string is a for valid email"""

    pattern = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b"

    # pass the regular expression
    # and the string into the fullmatch() method
    if re.fullmatch(pattern, email):
        return True

    else:
        raise InvalidEmail(email=email)

# %% ../../nbs/utils/convert.ipynb 26
def convert_string_to_bool(v):
    return v.lower() in ("yes", "true", "t", "1")

# %% ../../nbs/utils/convert.ipynb 28
class ConcatDataframe_InvalidElement(Exception):
    def __init__(self, elem):
        message = f"{type(elem)} passed into dataframe"
        super().__init__(message)


def concat_list_dataframe(df_ls: list[pd.DataFrame]) -> pd.DataFrame:
    """take a list of dataframes and collapse into one dataframe"""

    df = None
    for elem in df_ls:
        if not isinstance(elem, pd.DataFrame):
            raise ConcatDataframe_InvalidElement(elem)

        if len(elem.index) == 0:
            pass

        if df is None:
            df = elem

        else:
            df = pd.concat([df, elem], join="inner").reset_index(drop=True)
    return df

# %% ../../nbs/utils/convert.ipynb 30
def merge_dict(source : dict, destination: dict):
    """merge source dict into destination dict, merging values in destination"""
    for key, value in source.items():
        if isinstance(value, dict):
            # get node or create one
            node = destination.setdefault(key, {})
            merge_dict(value, node)
        else:
            destination[key] = value
            
    return destination
