# AUTOGENERATED FILE! PLEASE DON'T EDIT HERE. EDIT THE SOURCE NOTEBOOKS INSTEAD
"""
This is for support for transpilation of clis to JS
"""
__all__ = ["JsFunc", "toJsFunc"]
from k1lib.cli.init import BaseCli, fastF; import k1lib.cli.init as init
import k1lib.cli as cli; import k1lib, time, json, base64, math
settings = k1lib.settings.cli
settings.add("kjs", k1lib.Settings(), "cli.kjs settings")
kjsSett = settings.kjs
kjsSett.add("jsF",   {}, "All ._jsF() (cli to JS) transpile functions, looks like Dict[type -> _jsF(meta, **kwargs) transpile func]")
kjsSett.add("jsonF", {}, "All ._jsonF() JSON-serialization functions, looks like Dict[type -> _jsonF(obj) func]. See utils.deref.json() docs")
kjsSett.add("pyF",   {}, "(future feature) All ._pyF()   (cli to Python) transpile functions, looks like Dict[type -> _pyF  (meta, **kwargs) transpile func]")
kjsSett.add("cppF",  {}, "(future feature) All ._cppF()  (cli to C++)    transpile functions, looks like Dict[type -> _cppF (meta, **kwargs) transpile func]")
kjsSett.add("javaF", {}, "(future feature) All ._javaF() (cli to Java)   transpile functions, looks like Dict[type -> _javaF(meta, **kwargs) transpile func]")
kjsSett.add("sqlF",  {}, "(future feature) All ._sqlF()  (cli to SQL)    transpile functions, looks like Dict[type -> _sqlF (meta, **kwargs) transpile func]")
kjsSett.jsF[str] = lambda meta: (f"", "String")
kjsSett.jsF[int] = lambda meta: (f"", "parseInt")
kjsSett.jsF[float] = lambda meta: (f"", "parseFloat")
kjsSett.jsF[set] = lambda meta: (f"", "new Set")
kjsSett.jsF[list] = lambda meta: (f"", "Array.from")
kjsSett.jsF[tuple] = lambda meta: (f"", "Array.from")
kjsSett.jsF[abs] = lambda meta: (f"", "Math.abs")
kjsSett.jsF[json.loads] = lambda meta: (f"", "JSON.parse")
kjsSett.jsF[json.dumps] = lambda meta: (f"", "JSON.stringify")
kjsSett.jsF[base64.b64decode] = lambda meta: (f"", "atob")
kjsSett.jsF[base64.b64encode] = lambda meta: (f"", "btoa")
def _jst_round(meta, ndigits=None):                                              # _jst_round
    if ndigits is None: return "", f"Math.round"                                 # _jst_round
    else:                                                                        # _jst_round
        fIdx = init._jsFAuto(); dataIdx = init._jsDAuto()                        # _jst_round
        return f"const {fIdx} = ({dataIdx}) => Math.round(({dataIdx})*Math.pow(10, {ndigits})+Number.EPSILON)/Math.pow(10, {ndigits})", fIdx # _jst_round
kjsSett.jsF[round] = _jst_round                                                  # _jst_round
import math; kjsSett.jsF[math.floor] = lambda meta: (f"", "Math.floor")          # _jst_round
import html                                                                      # _jst_round
def _jsF_html_escape(meta):                                                      # _jsF_html_escape
    fIdx = init._jsFAuto(); dataIdx = init._jsDAuto()                            # _jsF_html_escape
    return f"const {fIdx} = ({dataIdx}) => {dataIdx}.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&#39;');", fIdx # _jsF_html_escape
kjsSett.jsF[html.escape] = _jsF_html_escape                                      # _jsF_html_escape
def _jsonF_range(obj):                                                           # _jsonF_range
    if obj.step != 1: return "[" + ", ".join([f"{e}" for e in obj]) + "]"        # _jsonF_range
    end = f".slice({obj.start})" if obj.start != 0 else ""                       # _jsonF_range
    return f"[...Array({obj.stop}).keys()]{end}"                                 # _jsonF_range
kjsSett.jsonF[range] = _jsonF_range                                              # _jsonF_range
def v(x): return x if isinstance(x, str) else json.dumps(x)                      # v
def vs(xs): return [x if isinstance(x, str) else json.dumps(x) for x in xs]      # vs
class vOld(BaseCli):                                                             # vOld
    def __init__(self, v:str):                                                   # vOld
        """Specifies a variable should be taken on the js side"""                # vOld
        super().__init__(); self.v = v                                           # vOld
    def __ror__(self, it): return NotImplemented                                 # vOld
    def __str__(self): return f"\ue157{self.v}\ue239" # for injection into grep, and other clis that wants to extract this var out of a completely normal string # vOld
    def __repr__(self): return f"<kjs.v v={self.v}>"                             # vOld
    @staticmethod                                                                # vOld
    def getValue(value):                                                         # vOld
        """If it's an instance of :class:`v`, then return its internal value, else
return the same object passed in. Useful for streamlining building out other clis""" # vOld
        return value.v if isinstance(value, v) else value                        # vOld
class Arg:                                                                       # Arg
    def __init__(self, arg):                                                     # Arg
        if isinstance(arg, str):                                                 # Arg
            name = arg; type_ = str; default = ""                                # Arg
        elif isinstance(arg, (tuple, list)):                                     # Arg
            if len(arg) == 3:                                                    # Arg
                name, type_, default = arg                                       # Arg
            elif len(arg) == 2:                                                  # Arg
                name, type_ = arg                                                # Arg
                if type_ == str: default = ""                                    # Arg
                if type_ == int: default = 1                                     # Arg
                if type_ == float: default = 1.0                                 # Arg
                if type_ == bool: default = True                                 # Arg
                if isinstance(type_, (range, k1lib.serve.slider)): default = round((type_.start + type_.stop)/2) # surprisingly no bug here where .step != 1, because <input type="range"> covered us # Arg
                if isinstance(type_, list): default = type_[0]                   # Arg
                if isinstance(type_, k1lib.serve.date): default = time.time() | cli.toIso() # Arg
            else: raise Exception(f"Argument has to specify 3 values: (name:str, type, default value), or 2 values: (name:str, type)") # Arg
        else: raise Exception(f"Can't parse argument `{arg}`. It must be either the argument name alone, or a tuple of (name:str, type, default value)") # Arg
        self.idx = cli.init._jsDAuto(); self.name = name; self.type_ = type_; self.default = default # Arg
    def html(self):                                                              # Arg
        idx = self.idx; name = self.name; t = self.type_; default = self.default # Arg
        if t == str: return f"<input type='text' value='{default}' placeholder='{name}' id='{idx}' style='padding: 4px 4px'>" # Arg
        if t == int or t == float: return f"<input type='number' value='{default}' placeholder='{name}' id='{idx}' style='padding: 4px 4px'>" # Arg
        if t == bool: return f"<input type='checkbox' {'checked' if default else ''} id='{idx}' style='padding: 4px 4px'>" # Arg
        if isinstance(t, (range, k1lib.serve.slider)): return f"<div style='display: flex; flex-direction: row; align-items: center'><input type='range' id='{idx}' min='{t.start}' max='{t.stop}' value='{default}' step='{t.step}' /><span id='{idx}_preview' style='margin-left: 8px'></span></div>" # Arg
        if isinstance(t, list):                                                  # Arg
            if default not in t: raise Exception(f"Dropdown with options {t} does not have the default option {default}") # Arg
            s = "".join([f"<option value='{i}' {'selected' if e == default else ''}>{e}</option>" for i, e in enumerate(t)]) # Arg
            return f"<select id='{idx}'>{s}</select>"                            # Arg
        if isinstance(t, k1lib.serve.date):                                      # Arg
            value = f"value='{default}'" if default else ""; minDate = f"min='{t.minDate}'" if t.minDate else ""; maxDate = f"max='{t.maxDate}'" if t.maxDate else "" # Arg
            return f'<input id="{idx}" type="datetime-local" {value} {minDate} {maxDate} />' # Arg
        raise Exception(f"Don't know type {t} on .html()")                       # Arg
    def value(self): # grab value of interface                                   # Arg
        idx = self.idx; name = self.name; t = self.type_; default = self.default # Arg
        q = f"document.querySelector('#{self.idx}')"                             # Arg
        if t == str: return f"{q}.value"                                         # Arg
        if t == int: return f"parseInt({q}.value || 0)"                          # Arg
        if t == float: return f"parseFloat({q}.value || 0.0)"                    # Arg
        if t == bool: return f"{q}.checked"                                      # Arg
        if isinstance(t, range): return f"parseInt({q}.value)"                   # Arg
        if isinstance(t, k1lib.serve.slider): return f"parseFloat({q}.value)"    # Arg
        if isinstance(t, list): return f"{json.dumps(t)}[{q}.value]"             # Arg
        if isinstance(t, k1lib.serve.date): return f"{q}.value"                  # Arg
        raise Exception(f"Don't know type {t} on .value()")                      # Arg
    def preview(self):                                                           # Arg
        idx = self.idx; name = self.name; t = self.type_; default = self.default # Arg
        if isinstance(t, range): return f"document.querySelector('#{self.idx}_preview').innerHTML = parseInt(document.querySelector('#{self.idx}').value);" # Arg
        if isinstance(t, k1lib.serve.slider):                                    # Arg
            delta = 10**(-math.floor(math.log10(t.step))) # figuring out the correct order of magnitude to round to # Arg
            return f"document.querySelector('#{self.idx}_preview').innerHTML = Math.round(document.querySelector('#{self.idx}').value*{delta})/{delta};" # Arg
        return ""                                                                # Arg
    def onchange(self, cb:str):                                                  # Arg
        idx = self.idx; name = self.name; t = self.type_; default = self.default # Arg
        q = f"document.querySelector('#{self.idx}')"                             # Arg
        if t == str or t == int or t == float: return f"{q}.oninput = {cb};"     # Arg
        if t == bool: return f"{q}.onclick = {cb};"                              # Arg
        if isinstance(t, (range, k1lib.serve.slider, list, k1lib.serve.date)): return f"{q}.oninput = {cb};" # Arg
        raise Exception(f"Don't know type {t} on .onchange()")                   # Arg
class _JsFuncInterface:                                                          # _JsFuncInterface
    def __init__(self, jsFunc, mode, args, debounce, delay):                     # _JsFuncInterface
        self.jsFunc = jsFunc; self.mode = mode; self.args = args; self.debounce = debounce; self.delay = delay # _JsFuncInterface
        if delay and delay < debounce: raise Exception(f"delay value ({delay}) is lower than debounce value ({debounce}), which doesn't make sense. Please raise delay, or lower the debounce value") # _JsFuncInterface
    def _repr_html_(self):                                                       # _JsFuncInterface
        pre = init._jsFAuto(); mode = self.mode; args = self.args; debounce = self.debounce*1000 # _JsFuncInterface
        postprocess = ""                                                         # _JsFuncInterface
        if mode == "html":                                                       # _JsFuncInterface
            res = "val"; postprocess = f"""
const containerDiv = document.createElement('div'); containerDiv.innerHTML = val;
for (const scriptElem of containerDiv.querySelectorAll('script')) eval(scriptElem.textContent);""" # _JsFuncInterface
        elif mode == "pre": res = "`<pre>${val}</pre>`"                          # _JsFuncInterface
        elif mode == "json": res = "`<pre>${JSON.stringify(val)}</pre>`"         # _JsFuncInterface
        elif mode == "jsone": res = "`<pre>${JSON.stringify(val, null, 2)}</pre>`" # _JsFuncInterface
        elif mode == "table": res = """ '<table>' + val.map((v) => "<tr>" + v.map((e) => `<td>${e}</td>`).join("") + "</tr>").join("") + '</table>' """ # _JsFuncInterface
        else: raise Exception(f"Unknown interface output mode {mode}")           # _JsFuncInterface
        inps = "".join([f"<div>{a.name}</div><div>{a.html()}</div>" for a in args]) # _JsFuncInterface
        inps = f"""<div style='display: grid; grid-template-columns: 1fr 3fr; width: 400px'>{inps}</div>""" # _JsFuncInterface
        onchanges = "".join([a.onchange(f"{pre}_reload") for a in args])         # _JsFuncInterface
        values = ", ".join([a.value() for a in args])                            # _JsFuncInterface
        preview = "\n".join([a.preview() for a in args])                         # _JsFuncInterface
        delayCode = f"(async () => {{ while (true) {{ await new Promise(r => setTimeout(r, {self.delay*1000})); await {pre}_reload_core(); }} }})();" if self.delay else "" # _JsFuncInterface
        return f"""\
<!-- k1lib.JsFuncInterface -->
<style>
    .{pre}_btn:hover {{
    }}
</style>
{inps}
<pre id="{pre}_error" style="color: red"></pre>
<div id="{pre}_loading" style="margin-top: 4px">&nbsp;</div>
<div id="{pre}_result"></div>
<script src="https://k1js.com/dist/amd/latest.js"></script>
<script>
    const {pre}_loadF = () => {{
        {self.jsFunc.fn}
        window.{self.jsFunc.fIdx} = {self.jsFunc.fIdx};
        {pre}_error = document.querySelector("#{pre}_error");
        {pre}_resultDiv = document.querySelector("#{pre}_result");
        {pre}_loadingDiv = document.querySelector("#{pre}_loading");
        const runningCoreHandle = [null];
        const {pre}_reload_core = async (e) => {{
            if (runningCoreHandle[0]) {{ runningCoreHandle[0].cancel = true; }}
            const handle = {{"cancel": false}}; runningCoreHandle[0] = handle;
            try {{
                {pre}_error.innerHTML = "";
                {pre}_loadingDiv.innerHTML = "(loading...)";
                const val = await {self.jsFunc.fIdx}({values});
                if (!handle.cancel) {{
                    {pre}_loadingDiv.innerHTML = "&nbsp;";
                    {pre}_resultDiv.innerHTML = {res};
                    {postprocess}
                }}
            }} catch (e) {{ if (!handle.cancel) {pre}_error.innerHTML = e.stack; }}
            runningCoreHandle[0] = null;
        }};
        let {pre}_reload_timeout = null;
        const {pre}_reload = async (e) => {{
            {preview}
            if ({debounce} <= 0) return {pre}_reload_core();
            else {{
                clearTimeout({pre}_reload_timeout);
                {pre}_reload_timeout = setTimeout({pre}_reload_core, {debounce})
            }}
        }}
        {onchanges}
        {delayCode}
        {pre}_reload(null);
    }};
    // trying multiple loading schemes, because in a live jupyter nb, it's using umd, while in an exported jupyter nb, it's using amd. The madness of JS
    (async () => {{
        try {{
            require(['k1js'], function(k1js) {{ (async () => {{ window.k1js = await k1js; {pre}_loadF(); }})(); }});
        }} catch (e) {{}}
    }})();
    (async () => {{
        try {{
            const res = await (await fetch("https://k1js.com/dist/umd/latest.js")).text();
            eval(res); window.k1js = await k1js; {pre}_loadF();
        }} catch (e) {{}}
    }})();
</script>"""                                                                     # _JsFuncInterface
pygments = k1lib.dep("pygments"); lexers = k1lib.dep("pygments.lexers"); formatters = k1lib.dep("pygments.formatters") # _JsFuncInterface
class JsFunc:                                                                    # JsFunc
    def __init__(self, fn:str, fIdx:str, args, _async):                          # JsFunc
        """Represents a generated function from :class:`toJsFunc`.
More docs are available at that class.

This is not supposed to be instantiated by the end user. Please
use :class:`toJsFunc` instead. It's here for documentation purposes only"""      # JsFunc
        self.fn = fn; self.fIdx = fIdx; self.args = args; self._async = _async   # JsFunc
    def __repr__(self):                                                          # JsFunc
        fn = self.fn.split('\n') | cli.head(300).all() | cli.join('\n')          # JsFunc
        return f"""<JsFunc {self.fIdx}>\n\nGenerated JS function:\n\n{fn}"""     # JsFunc
    def _repr_html_(self):                                                       # JsFunc
        fn = self.fn.split('\n') | cli.join('\n')                                # JsFunc
        header = html.escape(f"<JsFunc {self.fIdx}>") + "<br>Generated JS function:" # JsFunc
        try: return f"""{header}<br><br>{pygments.highlight(fn, lexers.JavascriptLexer(), formatters.HtmlFormatter())}""" # JsFunc
        except: return f"<pre style='overflow-x: auto'>{html.escape(repr(self))}</pre>" # JsFunc
    def interface(self, mode="html", debounce:float=0.03, delay:float=None):     # JsFunc
        """Creates an interface.

Different output modes:

- html: expected the js function to return html and will display it as-is
- pre: wraps the function result inside a "pre" tag, which makes html honor white spaces and whatnot
- json: json formats the function result in a compact way on 1 line
- jsone: "json expanded", same as above, but in an expanded way on multiple lines
- table: expects function result to be a table, then displays the table nicely

:param mode: see above
:param debounce: if specified, will wait for that many seconds until the user hasn't modified the input UIs.
    If function is not async (meaning it should executes quite fast), then this param will be ignored""" # JsFunc
        return _JsFuncInterface(self, mode, self.args, debounce if self._async else 0, delay) # JsFunc
class toJsFunc(BaseCli):                                                         # toJsFunc
    def __init__(self, *args, delay:float=None):                                 # toJsFunc
        """Capture cli operations and transpiles all of them to JS, to quickly
build functionalities in JS. Example::

    # creates an instance of class JsFunc that contains the transpiled js code
    jsf = range(10) | deref() | (toJsFunc("term") | head(kjs.v("term")) & head(6))
    # displays a search interface to jupyter notebook
    jsf.interface("json")

So a little more background on how this works. With clis, it can "capture" other
clis to the right of it if it desires. So, ``toJsFunc`` will capture the 2 ``head``s
and transpile them to js. So Python data never really gets passed through those later
heads. Read more about this at :class:`~k1lib.cli.init.BaseCli` and on the main
cli page at https://k1lib.com/latest/cli/index.html

The arguments passed in, namely "term" here, specifies what arguments should the JS
function have. Then you can reference those JS arguments downstream inside any cli,
like ``head(kjs.v("term"))``.

The input ``range(10) | deref()`` actually gets converted into json and injected as
data directly within the function, so it's pretty sweet. It does require the input
to be comprised of relatively simple data structures, with no arbitrary objects.

This is a relatively experimental/novel feature. The transpilation mechanism can
do a lot, but if it sees that something is too complicated, then it will raise an
error and complain, instead of failing silently.

When creating an interface, you can supply them with multiple different types of
UI:

.. pyexec:: html True

    arg1 = "extraText"
    arg2 = ("on", bool)
    arg3 = ("someNum", int)
    arg4 = ("n", range(0, 10, 2))
    arg5 = ("character", ["reimu", "marisa", "cirno"])
    jsF = list(range(100)) | (toJsFunc(arg1, arg2, arg3, arg4, arg5) | head("n") & head("on*10") | aS("[*x, character, someNum**2, len(extraText)*2]"))
    jsF.interface("json")
"""                                                                              # toJsFunc
        super().__init__(capture=True)                                           # toJsFunc
        self.args = [Arg(a) for a in args]                                       # toJsFunc
    def __ror__(self, it) -> JsFunc:                                             # toJsFunc
        header, fn, _async = k1lib.kast.asyncGuard(self.capturedSerial._jsF(("root",))) # used to be (kast, self.args) # toJsFunc
        header = "\n".join([f"    {e}" for e in header.split("\n")])             # toJsFunc
        fIdx = init._jsFAuto() + f"_{round(time.time())}"; dataIdx = init._jsDAuto() + f"_{round(time.time())}" # toJsFunc
        return JsFunc(f"""
const {dataIdx} = {it | cli.deref.js()};
async function {fIdx}({', '.join([a.name for a in self.args])}) {{
{header}
    return {'await ' if _async else ''}{fn}({dataIdx});
}}""", fIdx, self.args, _async)                                                  # toJsFunc
