# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/classes/50_DomoPage.ipynb.

# %% auto 0
__all__ = ['DomoPage_GetRecursive', 'DomoPage', 'DomoPages', 'Page_NoAccess']

# %% ../../nbs/classes/50_DomoPage.ipynb 2
from ..routes.page import PageRetrieval_byId_Error,Page_GET_Error,CRUD_Page_Error

# %% ../../nbs/classes/50_DomoPage.ipynb 3
from dataclasses import dataclass, field
from typing import List, Any

import httpx
import datetime as dt


import domolibrary.client.DomoError as dmde
import domolibrary.client.DomoAuth as dmda
from domolibrary.client.DomoEntity import DomoEntity_w_Lineage

import domolibrary.routes.page as page_routes

import domolibrary.utils.convert as dmcv
import domolibrary.utils.DictDot as util_dd
import domolibrary.utils.chunk_execution as dmce

import domolibrary.classes.DomoPage_Content as dmpg_c
import domolibrary.classes.DomoLineage as dmdl

from nbdev.showdoc import patch_to


# %% ../../nbs/classes/50_DomoPage.ipynb 7
class DomoPage_GetRecursive(dmde.ClassError):
    def __init__(
        self,
        cls,
        entity_id,
        auth: dmda.DomoAuth,

        include_recursive_children,
        include_recursive_parents,
    ):
        super().__init__(
            auth = auth,
            cls = cls,
            entity_id=entity_id,
            message=f"can only trace parents OR children recursively but not both. include_recursive_children : {include_recursive_children}, include_recursive_parents: {include_recursive_parents}",
        )


@dataclass
class DomoPage(DomoEntity_w_Lineage):
    id: int
    auth: dmda.DomoAuth = field(repr=False)
    Lineage: dmdl.DomoLineage = field(repr=False)
    
    title: str = None
    top_page_id: int = None
    parent_page_id: int = None
    is_locked: bool = None

    collections: list = field(default_factory=list)

    owners: list = field(default_factory=list)
    cards: list = field(default_factory=list)

    custom_attributes: dict = field(default_factory=dict)

    parent_page: dict = None  # DomoPage
    top_page: dict = None  # DomoPage
    children: list = field(default_factory=list)

    # parent_hierarchy: [dict] = None
    # flat_children: list = None

    layout: dmpg_c.PageLayout = field(default_factory=dict)

    cards: List["DomoCard"] = None
    datasets: List["DomoDataset"] = None


    def __post_init__(self):
        self.Lineage = dmdl.DomoLineage_Page._from_parent(
            parent=self
        )

    def display_url(self):
        return f"https://{self.auth.domo_instance}.domo.com/page/{self.id}"

    async def _get_domo_owners_from_dd(
        self,
        owners: util_dd.DictDot,
        suppress_no_results_error: bool = True,
        debug_api: bool = False,
        session: httpx.AsyncClient = None,
        debug_num_stacks_to_drop: int = 3,
    ):
        if not owners or len(owners) == 0:
            return []

        import domolibrary.classes.DomoUser as dmu
        import domolibrary.classes.DomoGroup as dmg

        domo_groups = []
        domo_users = []

        owner_group_ls = [
            owner.id for owner in owners if owner.type == "GROUP" and owner.id
        ]

        if len(owner_group_ls) > 0:
            domo_groups = await dmce.gather_with_concurrency(
                n=60,
                *[
                    dmg.DomoGroup.get_by_id(
                        group_id=group_id,
                        auth=self.auth,
                        debug_api=debug_api,
                        session=session,
                        debug_num_stacks_to_drop=debug_num_stacks_to_drop,
                    )
                    for group_id in owner_group_ls
                ],
            )

        owner_user_ls = [
            owner.id for owner in owners if owner.type == "USER" and owner.id
        ]

        if len(owner_user_ls) > 0:
            domo_users = await dmu.DomoUsers.by_id(
                user_ids=owner_user_ls,
                only_allow_one=False,
                auth=self.auth,
                session=session,
                debug_api=debug_api,
                debug_num_stacks_to_drop=debug_num_stacks_to_drop,
                suppress_no_results_error=suppress_no_results_error,
            )

        owner_ce = (domo_groups or []) + (domo_users or [])

        res = []
        for owner in owner_ce:
            if isinstance(owner, list):
                [res.append(member) for member in owner]
            else:
                res.append(owner)

        return res
    
        
    @classmethod
    async def _from_content_stacks_v3(
        cls,
        page_obj,
        suppress_no_results_error: bool = True,
        auth: dmda.DomoAuth = None,
        debug_api: bool = False,
        session: httpx.AsyncClient = None,
        debug_num_stacks_to_drop: int = 3,
    ):
        # import domolibrary.classes.DomoCard as dc

        dd = page_obj
        if isinstance(page_obj, dict):
            dd = util_dd.DictDot(page_obj)

        pg = cls(
            id=int(dd.id),
            title=dd.title,
            raw = page_obj,
            parent_page_id=int(dd.page.parentPageId) if dd.page.parentPageId else None,
            collections=dd.collections,
            auth=auth,
            Lineage = None,
        )

        if hasattr(dd, "pageLayoutV4") and dd.pageLayoutV4 is not None:
            pg.layout = dmpg_c.PageLayout._from_json(dd=dd.pageLayoutV4)

        if dd.page.owners and len(dd.page.owners) > 0:
            pg.owners = await pg._get_domo_owners_from_dd(
                dd.page.owners,
                suppress_no_results_error=suppress_no_results_error,
                debug_api=debug_api,
                session=session,
                debug_num_stacks_to_drop=debug_num_stacks_to_drop,
            )

        return pg
    

    @classmethod
    async def get_by_id(
        cls,
        page_id: str,
        auth: dmda.DomoAuth,
        suppress_no_results_error : bool = True,
        return_raw: bool = False,
        debug_api: bool = False,
        include_layout: bool = False,
        # if True, will drill down to all the Children.  Set to False to prevent calculating children
        debug_num_stacks_to_drop=2,
        session: httpx.AsyncClient = None,
        id=None,
    ):
        page_id = page_id or id

        res = await page_routes.get_page_by_id(
            auth=auth,
            page_id=page_id,
            debug_api=debug_api,
            include_layout=include_layout,
            session=session,
            parent_class=cls.__name__,
        )

        if return_raw:
            return res


        pg = await cls._from_content_stacks_v3(
            page_obj=res.response,
            auth=auth,
            session=session,
            debug_api=debug_api,
            suppress_no_results_error=suppress_no_results_error,
            debug_num_stacks_to_drop=debug_num_stacks_to_drop,
        )

        pg.custom_attributes["parent_page"] = None
        pg.custom_attributes["top_page"] = None

        return pg

    @classmethod
    async def _get_entity_by_id(
        cls,
        entity_id: str,
        **kwargs
    ):
        return await cls.get_by_id(
            page_id=entity_id,
            **kwargs
        )

# %% ../../nbs/classes/50_DomoPage.ipynb 9
@patch_to(DomoPage, cls_method=True)
async def _from_adminsummary(
    cls,
    page_obj,
    auth: dmda.DomoAuth,
    session: httpx.AsyncClient = None,
    debug_api: bool = False,
):
    import domolibrary.classes.DomoCard as dmc

    dd = page_obj

    if isinstance(page_obj, dict):
        dd = util_dd.DictDot(page_obj)

    page_id = int(dd.id or dd.pageId)

    parent_page_id = int(dd.parentPageId) if dd.parentPageId else page_id

    top_page_id = int(dd.topPageId) if dd.topPageId else parent_page_id

    pg = cls(
        id=page_id,
        title=dd.title or dd.pageTitle,
        raw=page_obj,
        parent_page_id=parent_page_id,
        top_page_id=top_page_id,
        collections=dd.collections,
        is_locked=dd.locked,
        auth=auth,
        Lineage=None,
    )

    if dd.page and dd.page.owners and len(dd.page.owners) > 0:
        pg.owners = await pg._get_domo_owners_from_dd(
            dd.page.owners, debug_api=debug_api, session=session
        )

    if dd.cards and len(dd.cards) > 0:
        pg.cards = await dmce.gather_with_concurrency(
            n=60,
            *[
                dmc.DomoCard.get_by_id(
                    id=card.id, auth=auth, session=session, debug_api=debug_api
                )
                for card in dd.cards
            ],
        )

    return pg


@patch_to(DomoPage, cls_method=True)
async def _from_bootstrap(
    cls: DomoPage,
    page_obj,
    auth: dmda.DomoAuth = None,
    debug_api: bool = False,
    session: httpx.AsyncClient = None,
):
    dd = page_obj
    if isinstance(page_obj, dict):
        dd = util_dd.DictDot(page_obj)

    pg = cls(id=int(dd.id), title=dd.title, raw=page_obj, auth=auth, Lineage = None)

    if isinstance(dd.owners, list) and len(dd.owners) > 0:
        pg.owners = await pg._get_domo_owners_from_dd(
            dd.owners, debug_api=debug_api, session=session
        )

    if isinstance(dd.children, list) and len(dd.children) > 0:
        pg.children = await dmce.gather_with_concurrency(
            n=60,
            *[
                cls._from_bootstrap(
                    page_obj=child_dd, auth=auth, debug_api=debug_api, session=session
                )
                for child_dd in dd.children
                if child_dd.type == "page"
            ],
        )

        [print(other_dd) for other_dd in dd.children if other_dd.type != "page"]

    return pg

# %% ../../nbs/classes/50_DomoPage.ipynb 11
@dataclass
class DomoPages:
    auth: dmda.DomoAuth = field(repr=False)
    pages: List[DomoPage] = None

    async def get(self, **kwargs):
        """calls get_admin_summary to retrieve all pages in an instance"""
        return await self.get_admin_summary(**kwargs)

    async def get_admin_summary(
        self,
        search_title: str = None,
        parent_page_id: int = None,
        return_raw: bool = False,
        debug_api: bool = False,
        session: httpx.AsyncClient = None,
    ):
        """use admin_summary to retrieve all pages in an instance -- regardless of user access
        NOTE: some Page APIs will not return results if page access isn't explicitly shared
        """

        res = await page_routes.get_pages_adminsummary(
            auth=self.auth,
            debug_api=debug_api,
            session=session,
            search_title=search_title,
            page_parent_id=parent_page_id,
        )

        if return_raw:
            return res

        self.pages = await dmce.gather_with_concurrency(
            n=60,
            *[
                DomoPage._from_adminsummary(
                    page_obj, auth=self.auth, debug_api=debug_api, session=session
                )
                for page_obj in res.response
            ],
        )

        return self.pages

# %% ../../nbs/classes/50_DomoPage.ipynb 15
@patch_to(DomoPage)
async def get_children(self: DomoPage, is_suppress_errors: bool = False):

    async def _get_children_recur(parent_page, is_suppress_errors: bool = False):

        parent_page.children = parent_page.children or []

        try:
            child_pages = await DomoPages(auth=parent_page.auth).get(
                parent_page_id=parent_page.id, 
                # is_suppress_errors=is_suppress_errors
            )

            parent_page.children = [
                child
                for child in child_pages
                if child is not None and child.parent_page_id == parent_page.id
            ]

            await dmce.gather_with_concurrency(
                n=10,
                *[
                    _get_children_recur(
                        parent_page=cp,
                        is_suppress_errors=is_suppress_errors,
                    )
                    for cp in parent_page.children
                ],
            )

            return self.children

        except dmde.DomoError as e:
            print(
                f"cannot access child page -- https://{parent_page.auth.domo_instance}.domo.com/page/{page_id} -- is it shared with you?"
            )
            if not is_suppress_errors:
                raise e from e

    self.children = await _get_children_recur(
        parent_page=self,
        is_suppress_errors=is_suppress_errors,
    )

    return self.children


@patch_to(DomoPage)
def flatten_children(self: DomoPage, path=None, hierarchy=0, results=None):
    results = results or []

    path = f"{path} > {self.title}" if path else self.title

    results.append({"hierarchy": hierarchy, "path": path, "page": self})

    if self.children:
        [
            child.flatten_children(path, hierarchy + 1, results)
            for child in self.children
        ]

    return results

# %% ../../nbs/classes/50_DomoPage.ipynb 18
@patch_to(DomoPage)
async def get_parents(self: DomoPage,page = None):

    page = page or self

    if not page.parent_page_id:
        return self.custom_attributes
    
    if not self.top_page_id: 
        page_as = next((pg for pg in (await DomoPages(auth = self.auth).get(search_title=page.id)) if pg.id ==page.id))
        page.top_page_id =  page_as.top_page_id
        self.top_page = page_as
        
    if page.id == page.top_page_id:
        self.custom_attributes['top_page'] = page

    parent_page_as = next((pg for pg in (await DomoPages(auth = self.auth).get(search_title=page.parent_page_id)) if pg.id == page.parent_page_id))

    if self.parent_page_id == parent_page_as.id:
        self.parent_page = parent_page_as

    page.custom_attributes["parent_page"] = parent_page_as
    
    if not self.custom_attributes.get('path'):
        self.custom_attributes["path"] = []

    self.custom_attributes['path'].append(parent_page_as)

    if page.id != self.top_page_id:
        await self.get_parents(page = parent_page_as)

    return self.custom_attributes



# %% ../../nbs/classes/50_DomoPage.ipynb 20
class Page_NoAccess(dmde.DomoError):
    def __init__(self, page_id, page_title, domo_instance, function_name, parent_class):
        super().__init__(
            function_name=function_name,
            parent_class=parent_class,
            domo_instance=domo_instance,
            message=f'authenticated user doesn\'t have access to {page_id} - "{page_title}" contact owners to share access',
        )

# %% ../../nbs/classes/50_DomoPage.ipynb 21
@patch_to(DomoPage)
async def test_page_access(
    self: DomoPage,
    suppress_no_access_error: bool = False,  # suppresses error if user doesn't have access
    debug_api: bool = False,
    return_raw: bool = False,
    session: httpx.AsyncClient = None,
    debug_num_stacks_to_drop=2,
):
    """throws an error if user doesn't have access to the page
    API returns the owners of the page
    """

    res = await page_routes.get_page_access_test(
        auth=self.auth,
        page_id=self.id,
        session=session,
        debug_api=debug_api,
        debug_num_stacks_to_drop=debug_num_stacks_to_drop,
        parent_class= self.__class__.__name__
    )

    if return_raw:
        return res

    try:
        page_access = res.response.get("pageAccess")

        if not page_access:
            raise Page_NoAccess(
                page_id=self.id,
                page_title=self.title,
                domo_instance=self.auth.domo_instance,
                function_name=res.traceback_details.function_name,
                parent_class=self.__class__.__name__,
            )

    except Page_NoAccess as e:
        print(e)

        if not suppress_no_access_error:
            raise e

    return res

# %% ../../nbs/classes/50_DomoPage.ipynb 25
@patch_to(DomoPage)
async def get_accesslist(
    self,
    auth: dmda.DomoAuth = None,
    return_raw: bool = False,
    debug_api: bool = False,
    session: httpx.AsyncClient = None,
    debug_num_stacks_to_drop=2,
):
    auth = auth or self.auth

    res = await page_routes.get_page_access_list(
        auth=auth,
        is_expand_users=True,
        page_id=self.id,
        debug_api=debug_api,
        debug_num_stacks_to_drop=debug_num_stacks_to_drop,
        session=session,
        parent_class=self.__class__.__name__,
    )

    if return_raw:
        return res


    import domolibrary.classes.DomoUser as dmu
    import domolibrary.classes.DomoGroup as dmg

    s = {
        "explicit_shared_user_count": res.response.get("explicitSharedUserCount"),
        "total_user_count": res.response.get("totalUserCount"),
    }

    user_ls = res.response.get("users", None)
    domo_users = []
    
    if user_ls and isinstance(user_ls, list) and len(user_ls) > 0:
        domo_users = await dmu.DomoUsers.by_id(
            user_ids=[user.get("id") for user in user_ls],
            only_allow_one=False,
            auth=auth,
            session=session,
            debug_num_stacks_to_drop=debug_num_stacks_to_drop + 1,
        )

    group_ls = res.response.get("groups", None)
    domo_groups = []
    if group_ls and isinstance(group_ls, list) and len(group_ls) > 0:
        domo_groups = await dmce.gather_with_concurrency(
            n=60,
            *[
                dmg.DomoGroup.get_by_id(
                    group_id=group.get("id"),
                    auth=auth,
                    session=session,
                    debug_num_stacks_to_drop=debug_num_stacks_to_drop + 1,
                )
                for group in group_ls
            ],
        )

    res = await self.test_page_access(suppress_no_access_error=True)
    owner_ls = res.response["owners"]  # from test_page_access

    for domo_user in domo_users:
        # isExplicitShare is set by the get_access_list API response
        domo_user.custom_attributes["is_explicit_share"] = next(
            (
                user_obj["isExplicitShare"]
                for user_obj in user_ls
                if int(user_obj.get("id")) == int(domo_user.id)
            )
        )

        # group membership is determined by get_access_list API response
        domo_user.custom_attributes["group_membership"] = [
            domo_group
            for group_obj in group_ls
            for domo_group in domo_groups
            if int(domo_user.id)
            in [int(user_obj["id"]) for user_obj in group_obj.get("users")]
            and domo_group.id == group_obj["id"]
        ]

        # isOwner determined by test_access API response and group membership
        domo_user.custom_attributes["is_owner"] = False

        # test ownership as a user
        match_owner = next(
            (
                owner_obj
                for owner_obj in owner_ls
                if int(owner_obj["id"]) == int(domo_user.id)
                and owner_obj["type"] == "USER"
            ),
            None,
        )

        match_group = next(
            (
                owner_obj
                for owner_obj in owner_ls
                if int(owner_obj["id"])
                in [
                    int(domo_group.id)
                    for domo_group in domo_user.custom_attributes["group_membership"]
                ]
                and owner_obj["type"] == "GROUP"
            ),
            None,
        )

        if match_owner or match_group:
            domo_user.custom_attributes["is_owner"] = True

    # group ownership is confirmed test_access API
    for domo_group in domo_groups:
        match_owner = next(
            (
                owner_obj
                for owner_obj in owner_ls
                if int(owner_obj["id"]) == int(domo_group.id)
                and owner_obj["type"] == "GROUP"
            ),
            None,
        )

        domo_group.custom_attributes["is_owner"] = True if match_owner else False

    return {
        **s,
        "domo_users": domo_users,
        "domo_groups": domo_groups,
    }

# %% ../../nbs/classes/50_DomoPage.ipynb 28
@patch_to(DomoPage)
async def share(
    self: DomoPage,
    auth: dmda.DomoAuth = None,
    domo_users: list = None,  # DomoUsers to share page with,
    domo_groups: list = None,  # DomoGroups to share page with
    message: str = None,  # message for automated email
    debug_api: bool = False,
    session: httpx.AsyncClient = None,
):
    import domolibrary.routes.datacenter as datacenter_routes

    if domo_groups:
        domo_groups = domo_groups if isinstance(domo_groups, list) else [domo_groups]
    if domo_users:
        domo_users = domo_users if isinstance(domo_users, list) else [domo_users]

    res = await datacenter_routes.share_resource(
        auth=auth or self.auth,
        resource_ids=[self.id],
        resource_type=datacenter_routes.ShareResource_Enum.PAGE,
        group_ids=[group.id for group in domo_groups] if domo_groups else None,
        user_ids=[user.id for user in domo_users] if domo_users else None,
        message=message,
        debug_api=debug_api,
        session=session,
    )

    return res

# %% ../../nbs/classes/50_DomoPage.ipynb 31
@patch_to(DomoPage)
async def get_cards(
    self,
    debug_api: bool = False,
    session: httpx.AsyncClient = None,
    return_raw : bool = False
):
    import domolibrary.classes.DomoCard as dc

    res = await page_routes.get_page_definition(
        auth=self.auth, page_id=self.id, debug_api=debug_api, session=session
    )

    if return_raw: 
        return res

    if len(res.response.get("cards")) == 0:
        return []

    self.domo_cards = await dmce.gather_with_concurrency(
        n=60,
        *[
            dc.DomoCard.get_by_id(card_id=card["id"], auth=self.auth)
            for card in res.response.get("cards")
        ],
    )

    return self.domo_cards

@patch_to(DomoPage)
async def get_datasets(
    self,
    debug_api: bool = False,
    session: httpx.AsyncClient = None,
    return_raw: bool = False
):
    import domolibrary.classes.DomoDataset as dmds

    res = await page_routes.get_page_definition(
        auth=self.auth, page_id=self.id, debug_api=debug_api, session=session
    )

    if return_raw:
        return res

    cards = await self.get_cards()

    card_datasets = await dmce.gather_with_concurrency(*[
        card.get_datasets(debug_api = debug_api, session = session) for card in cards], n = 10)

    self.datasets = [ds for ds_ls in card_datasets for ds in ds_ls if ds is not None]
    
    return self.datasets

# %% ../../nbs/classes/50_DomoPage.ipynb 34
@patch_to(DomoPage, cls_method=True)
async def update_layout(
    cls, auth: dmda.DomoAuth, body: dict, layout_id: str, debug_api: bool = False
):
    datetime_now = dt.datetime.now()
    start_time_epoch = dmcv.convert_datetime_to_epoch_millisecond(datetime_now)

    res_writelock = await page_routes.put_writelock(
        auth=auth,
        layout_id=layout_id,
        user_id=auth.user_id,
        epoch_time=start_time_epoch,
    )
    if res_writelock.status == 200:
        res = await page_routes.update_page_layout(
            auth=auth, body=body, layout_id=layout_id, debug_api=debug_api
        )

        if not res.is_success:
            return False

        res_writelock = await page_routes.delete_writelock(
            auth=auth, layout_id=layout_id
        )
        if res_writelock.status != 200:
            return False

    else:
        return False

    return True

# %% ../../nbs/classes/50_DomoPage.ipynb 37
@patch_to(DomoPage,)
async def add_owner(
    self,
    group_id_ls: List[int],  # DomoGroup IDs to share page with
    user_id_ls: List[int],  # DomoUser IDs to share page with
    note: str = None,  # message for automated email
    send_email: bool = False,  # send or not email to the new owners
    debug_api: bool = False,
    session: httpx.AsyncClient = None,
):
    res = await page_routes.add_page_owner(
        auth=self.auth,
        page_id_ls=[self.id],
        group_id_ls=group_id_ls,
        user_id_ls=user_id_ls,
        note=note,
        send_email=send_email,
        debug_api=debug_api,
        session=session,
    )

    return res
