"""Rich display utilities and dynamic CSS for Jupyter notebooks"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/17_display.ipynb.

# %% ../nbs/17_display.ipynb 1
from __future__ import annotations


# %% auto 0
__all__ = ['selector', 'at_rule', 'ruleset', 'pretty_repr', 'rich_display', 'RenderJSON', 'cssmap', 'GlobalCSS']

# %% ../nbs/17_display.ipynb
import json
import uuid
from typing import Mapping
from typing import overload
from typing import Sequence
from typing import TypeAlias

import fastcore.all as FC
import rich
from IPython.display import display
from IPython.display import DisplayHandle
from IPython.display import HTML


# %% ../nbs/17_display.ipynb
@overload
def pretty_repr(*o, html:bool=True, text:bool=False, **kwargs) -> str: ...
@overload
def pretty_repr(*o, html:bool=False, text:bool=True, **kwargs) -> str: ...
def pretty_repr(*o, html:bool=True, text:bool=True, **kwargs) -> dict[str, str]|str:
    from rich.pretty import Pretty
    d = Pretty(*o, **kwargs)._repr_mimebundle_(
        include=((),('text/plain',))[text] + ((),('text/html',))[html], 
        exclude=((),('text/plain',))[not text] + ((),('text/html',))[not html]
        )
    return d if len(d) > 1 else tuple(d.values())[0]

# %% ../nbs/17_display.ipynb
def rich_display(*o, dhdl: DisplayHandle|None=None):
    if not o: return
    vv:tuple[str, ...] = tuple(FC.flatten([_.items() for _ in map(pretty_repr, o)]))  # type: ignore
    dd = {'text/plain':'\n'.join(vv[1::4]), 'text/html':'\n'.join(vv[3::4])}
    if dhdl: dhdl.update(dd, raw=True)
    else: display(dd, raw=True)

# %% ../nbs/17_display.ipynb
class RenderJSON(object):
    def __init__(self, json_data, max_height=200, init_level=0):
        if isinstance(json_data, Sequence):
            s = json.dumps(list(json_data))
        elif isinstance(json_data, Mapping):
            s = json.dumps(dict(json_data))
        elif hasattr(json_data, 'to_dict'):
            s = json.dumps(json_data.to_dict())
        elif hasattr(json_data, 'to_json'):
            s = json_data.to_json()
        else:
            s = json_data
        self.json_str = s
        self.uuid = str(uuid.uuid4())
        self.max_height = max_height
        self.init_level = init_level

    def display(self):
        html_content = f"""
<div id="wrapper-{self.uuid}" style="width: 100%; max-height: {self.max_height}px; overflow-y: auto;">
    <div id="{self.uuid}" style="width: 100%;"></div>
    <script>
        function renderMyJson() {{
            renderjson.set_show_to_level({self.init_level});
            document.getElementById('{self.uuid}').appendChild(renderjson({self.json_str}));
        }};
        function loadRenderjson() {{
            if (window.renderjson) return Promise.resolve();
            return new Promise((resolve, reject) => {{
                const script = document.createElement('script');
                script.src = 'https://cdn.jsdelivr.net/npm/renderjson@latest/renderjson.js';
                script.onload = resolve;
                script.onerror = reject;
                document.head.appendChild(script);
            }});
        }};
        loadRenderjson().then(renderMyJson).catch(err => console.error('Failed to load renderjson:', err));
    </script>
</div>
"""
        display(HTML(html_content), metadata={'bridge': {'skip':True}})

    def _ipython_display_(self):
        self.display()

# %% ../nbs/17_display.ipynb
selector: TypeAlias = str
at_rule: TypeAlias = str
ruleset: TypeAlias = dict[str, str]

@overload
def cssmap(stylesheet: dict[selector, ruleset], lvl: int = 0) -> str: ...

@overload
def cssmap(stylesheet: dict[at_rule, dict[selector, ruleset]], lvl: int = 0) -> str: ...

def cssmap(stylesheet, lvl: int = 0) -> str:
    def format_ruleset(rset: ruleset) -> str: 
        indent = '  ' * (lvl + 1)
        return ';\n'.join(f'{indent}{k}: {v}' for k, v in rset.items())
    
    def format_block(sel: str, content: str) -> str: 
        indent = '  ' * lvl
        return f'{indent}{sel} {{\n{content}\n{indent}}}'
    
    css_blocks = [
        f'{selector} {{\n{cssmap(rules, lvl+1)}\n{" "*lvl}}}' 
        if selector.startswith('@') else 
        format_block(selector, format_ruleset(rules))
        for selector, rules in stylesheet.items()
    ]
    
    return '\n\n'.join(css_blocks)

# %% ../nbs/17_display.ipynb
class GlobalCSS:
    _style_tmpl = '<style id="{name}">{css}</style>'
    def _html_widget(self, name:str, css:str): return self._style_tmpl.format(name=name, css=css)
    def has_style(self, name:str): return name in self._name2n
    @property
    def css(self): return '\n'.join(self._css)

    def add(self, name:str, css, update: bool=False):
        if self.has_style(name):
            if not update: return
        else:
            n = len(self._css)
            self._name2n[name] = n
            self._css.append('')
        self.update(name, css)

    def update(self, name:str, css):
        css = css if isinstance(css, str) else FC.valmap(css)
        if css and (n := self._name2n.get(name)) is not None:
            self._css[n] = self._html_widget(name=name, css=css)
            if self._dh:
                self._dh.update(HTML(self.css))
    
    _dh: DisplayHandle
    def display(self): 
        if self._dh is None: self._dh = display(HTML(self.css), display_id=True)  # type: ignore
        else: print('GlobalCSSs should be displayed only once, skipping.')
    def _ipython_display_(self): self.display()

    def __init__(self, css:dict[str, str] | None = None):
        self._ui_done = False
        self._dh = None  # type: ignore
        css = css or {}
        self._name2n = {name: n for n,name in enumerate(css.keys())}
        self._css = [self._html_widget(name, css) for name,css in css.items()]
