# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/routes/codeengine.ipynb.

# %% auto 0
__all__ = ['CodeEngine_API_Error', 'get_packages', 'CodeEngine_Package_Parts', 'get_package_versions',
           'get_codeengine_package_by_id_and_version', 'remove_javascript_comments',
           'parse_javascript_between_functions', 'parse_javascript_between_module', 'remove_after_closing_parens',
           'parse_javascript', 'parse_python', 'parse_functions_factory', 'extract_functions']

# %% ../../nbs/routes/codeengine.ipynb 2
import httpx

from domolibrary.client.DomoEntity import DomoEnum
from typing import List, Callable
from functools import partial

import re

import domolibrary.client.get_data as gd
import domolibrary.client.DomoError as dmde
import domolibrary.client.ResponseGetData as rgd
import domolibrary.client.DomoAuth as dmda

# %% ../../nbs/routes/codeengine.ipynb 5
class CodeEngine_API_Error(dmde.DomoError):
    def __init__(self, res : rgd.ResponseGetData ):
        super().__init__(res = res)



@gd.route_function
async def get_packages(
    auth: dmda.DomoAuth,
    debug_api: bool = False,
    debug_num_stacks_to_drop=1,
    session: httpx.AsyncClient = None,
    parent_class: str = None,
):
    url = f"http://{auth.domo_instance}.domo.com/api/codeengine/v2/packages"

    res = await gd.get_data(
        url=url,
        auth=auth,
        method="get",
        debug_api=debug_api,
        num_stacks_to_drop=debug_num_stacks_to_drop,
        session=session,
        parent_class=parent_class,
        is_follow_redirects=True
    )

    if not res.is_success:
        raise CodeEngine_API_Error(res = res)

    return res

# %% ../../nbs/routes/codeengine.ipynb 7
class CodeEngine_Package_Parts(DomoEnum):
    VERSIONS = 'versions'
    FUNCTIONS = 'functions'
    CODE = 'code'

# %% ../../nbs/routes/codeengine.ipynb 8
class CodeEngine_FunctionCallError(dmde.DomoError):
    def __init__(self, message: str, auth: dmda.DomoAuth):
        super().__init__(message=message, domo_instance=auth.domo_instance)


@gd.route_function
async def get_codeengine_package_by_id(
    package_id,
    auth: dmda.DomoAuth,
    debug_api: bool = False,
    params: dict = None,
    session: httpx.AsyncClient = None,
    parent_class: str = None,
    debug_num_stacks_to_drop=1,
) -> rgd.ResponseGetData:
    url = (
        f"https://{auth.domo_instance}.domo.com/api/codeengine/v2/packages/{package_id}"
    )

    if not package_id:
        raise CodeEngine_FunctionCallError(
            message="Package ID must be provided.",
            auth=auth,
        )
    
    params = params or {"parts": "versions"}

    res = await gd.get_data(
        auth=auth,
        url=url,
        method="GET",
        debug_api=debug_api,
        params=params,
        session=session,
        parent_class=parent_class,
        num_stacks_to_drop=debug_num_stacks_to_drop,
    )

    if not res.is_success:
        raise CodeEngine_API_Error(res=res)

    return res

# %% ../../nbs/routes/codeengine.ipynb 10
@gd.route_function
async def get_package_versions(
    auth: dmda.DomoAuth,
    package_id,
    debug_api: bool = False,
    parent_class: str = None,
    debug_num_stacks_to_drop=1,
    session : httpx.AsyncClient = None
):
    """each package can have one or many version"""

    if not package_id:
        raise CodeEngine_FunctionCallError(
            message="Package ID must be provided.",
            auth=auth,
        )

    url = f"https://{auth.domo_instance}.domo.com/api/codeengine/v2/packages/{package_id}/versions/"

    params = {"parts": "functions,code"}

    res = await gd.get_data(
        url = url,
        method ="get",
        auth=auth,
        params=params,
        debug_api=debug_api,
        num_stacks_to_drop = debug_num_stacks_to_drop,
        parent_class = parent_class,
        session = session
    )

    if not res.is_success:
        raise CodeEngine_API_Error(res = res)
    
    return res

# %% ../../nbs/routes/codeengine.ipynb 12
@gd.route_function
async def get_codeengine_package_by_id_and_version(
    package_id,
    version,
    auth: dmda.DomoAuth,
    debug_api: bool = False,
    params: dict = None,
    session: httpx.AsyncClient = None,
    parent_class: str = None,
    debug_num_stacks_to_drop=1,
) -> rgd.ResponseGetData:
    
    if not package_id or not version:
        raise CodeEngine_FunctionCallError(
            message= f"Package ID {package_id or 'not provided '} and version {version or ' not provided  '} must be provided.",
            auth=auth,
        )
    
    url = f"https://{auth.domo_instance}.domo.com/api/codeengine/v2/packages/{package_id}/versions/{version}"

    params = params or {"parts": "functions,code"}

    res = await gd.get_data(
        auth=auth,
        url=url,
        method="GET",
        debug_api=debug_api,
        params=params,
        session=session,
        parent_class=parent_class,
        num_stacks_to_drop=debug_num_stacks_to_drop,
    )

    if not res.is_success:
        raise CodeEngine_API_Error(res=res)
    
    return res

# %% ../../nbs/routes/codeengine.ipynb 16
def remove_javascript_comments(code, **kwargs):
    pattern = r"(/\*\*(.*?)\*/)"
    return re.sub(pattern, "", code, flags=re.DOTALL)


def parse_javascript_between_functions(code, function_name):
    # pattern = r"((async\s)?function\s+" + function_name + r"\(.*?\).*?)(async\s)?function\s"
    pattern = r"((async\s)?function\s+" + function_name + r"\(.*?\).*?}\n\n\n)"
    match = re.search(pattern, code, re.DOTALL)
    return match.group(0) if match else None


def parse_javascript_between_module(code, function_name):
    pattern = (
        r"((async\s)?function\s+" + function_name + r"\(.*?\).*?)(?=module.export)"
    )
    match = re.search(pattern, code, re.DOTALL)
    return match.group(0) if match else None


def remove_after_closing_parens(code, **kwargs):
    pattern = r"(.*\})"
    match = re.search(pattern, code, re.DOTALL)
    return match.group(0) if match else None


def parse_javascript(function_name, code):
    # remove comments
    code = remove_javascript_comments(code)

    # find everything until the next function definition
    match_str = parse_javascript_between_functions(
        code=code, function_name=function_name
    )

    if not match_str:
        match_str = parse_javascript_between_module(
            code=code, function_name=function_name
        )

    # remove everything after the last closing parens
    return remove_javascript_comments(match_str)


def parse_python(function_name, code):
    pattern = (
        r"(async\s+)?def\s+" + re.escape(function_name) + r"\s*\([^)]*\)\s*:\s*[^}]*"
    )
    match = re.search(pattern, code, re.DOTALL)
    return match.group(0)


class parse_functions_factory(DomoEnum):
    PYTHON = partial(parse_python)
    JAVASCRIPT = partial(parse_javascript)


def extract_functions(
    function_ls: List[dict],  # from API, function metadata
    code: str,  # from API, code string
    language: str,  # PYTHON or JAVASCRIPT, must align with parse_functions_factory enum
    parse_fn: Callable = None,  # function for exctracting functions must receive `code:str` and function_name:str`
):
    """helper function that handles parsing"""

    parse_fn = parse_fn or parse_functions_factory[language]

    res = []

    # return [{ **function, "code" : parse_fn.value(function_name=function["name"], code=code)}
    #     for function in function_ls
    # ]
    for function in function_ls:
        function_name = function["name"]
        # print(function_name)
        code_obj = {"code": parse_fn.value(function_name=function_name, code=code)}
        # print(code_obj)
        res.append({**function, **code_obj})

    return res
