"""Utilities for ipywidgets: cleanup, async interaction, and blocking input"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/20_widgets.ipynb.

# %% ../nbs/20_widgets.ipynb 1
from __future__ import annotations

# %% auto 0
__all__ = ['close_widget', 'cleanupwidgets', 'Clickable', 'wait_for_change', 'yield_for_change', 'get_user_input']

# %% ../nbs/20_widgets.ipynb
import asyncio
import time
from functools import wraps

import ipywidgets as W
import traitlets as T
from IPython.display import display
from IPython.display import Markdown
from jupyter_ui_poll import ui_events


# %% ../nbs/20_widgets.ipynb
from .basic import _get_globals


# %% ../nbs/20_widgets.ipynb
def close_widget(w: W.Widget, all: bool=True):
    if all:
        for k in w.keys:  # type: ignore
            if isinstance((v := getattr(w, k)), W.Widget):
                close_widget(v)
    w.close()

# %% ../nbs/20_widgets.ipynb
def cleanupwidgets(*ws, mod: str|None=None, clear=True, all=True):
    from IPython.display import clear_output
    glb = _get_globals(mod or __name__)
    for w in ws:
        _w = glb.get(w) if isinstance(w, str) else w
        if _w:
            try: close_widget(_w, all=all)
            except Exception as e: pass
    if clear: clear_output(wait=False)

# %% ../nbs/20_widgets.ipynb
class Clickable(W.Button, W.ValueWidget):
    "Button with value."
    value = T.Int(0)
    def __init__(self, description='', **kwargs):
        super().__init__(description=description, **kwargs)
        self.on_click(lambda b: b.set_trait('value', b.value + 1))
        if not description:
            T.dlink((self, 'value'), (self, 'description'), lambda x: f'{x}')

# %% ../nbs/20_widgets.ipynb
def wait_for_change(widget:W.Widget, value:str):
    future = asyncio.Future()
    def getvalue(change):
        future.set_result(change.new)
        widget.unobserve(getvalue, value)
    widget.observe(getvalue, value)
    return future

# %% ../nbs/20_widgets.ipynb
def yield_for_change(widget, attribute):
    """Pause a generator to wait for a widget change event.
        
    This is a decorator for a generator function which pauses the generator on yield
    until the given widget attribute changes. The new value of the attribute is
    sent to the generator and is the value of the yield.
    """
    def f(iterator):
        @wraps(iterator)
        def inner():
            i = iterator()
            def next_i(change):
                try: i.send(change.new)
                except StopIteration as e: widget.unobserve(next_i, attribute)
            widget.observe(next_i, attribute)
            # start the generator
            next(i)
        return inner
    return f

# %% ../nbs/20_widgets.ipynb
def get_user_input(
        prompt='', placeholder='Write something. Enter to submit', timeout=10., widget=None, value=None):
    if widget is None:
        layout = {'description_width':'auto', 'width':'80%'}
        b = W.HBox([
            W.HTML(f"<b>{prompt}</b>" if prompt else ''),
            w := W.Text(value=value,placeholder=placeholder, continuous_update=False, layout=layout)
        ])
        dh = display(b, display_id=True); time.sleep(0.05)
    else: w, dh = widget, None
    w.focus()
    answer = v = w.value
    start_time = time.time()
    with ui_events() as ui_poll:
        while answer == v:
            if (time.time() - start_time) > timeout: break
            ui_poll(10)
            time.sleep(0.1)  # Simulate async processing
            answer = w.value
    if dh: b.close(); dh.update(Markdown(f"**{prompt}** {answer}" if prompt else answer))
    return answer.lower()
