"""Feature-complete ECharts components for FastHTML — with JS formatters, HTMX click events, theme support, and memory cleanup."""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.

# %% auto #0
__all__ = ['DEFAULT_ECHARTS_VERSION', 'echarts_header', 'JSFunc', 'EChartsEncoder', 'EChart', 'preview_echart', 'EChartUpdate',
           'EChartJS', 'EChartOOB']

# %% ../nbs/00_core.ipynb #3460de9a
import json
import uuid

from fasthtml.common import Script, Div, NotStr, to_xml

# %% ../nbs/00_core.ipynb #43a84606
DEFAULT_ECHARTS_VERSION = "5.5.0"

def echarts_header(version=DEFAULT_ECHARTS_VERSION):
    "Include the ECharts CDN script tag in your FastHTML app headers."
    return Script(src=f"https://cdn.jsdelivr.net/npm/echarts@{version}/dist/echarts.min.js")

# %% ../nbs/00_core.ipynb #b5e6aaee
class JSFunc(str):
    "Marker class to flag a string as a raw JavaScript function for ECharts options."

# %% ../nbs/00_core.ipynb #e026f1ba
class EChartsEncoder(json.JSONEncoder):
    "Custom JSON encoder that flags `JSFunc` values with a `!JS!` prefix so the frontend can revive them."
    def encode(self, o):
        return super().encode(self._convert(o))

    def _convert(self, o):
        if isinstance(o, JSFunc): return f"!JS!{str(o)}"
        if isinstance(o, dict): return {k: self._convert(v) for k, v in o.items()}
        if isinstance(o, (list, tuple)): return [self._convert(i) for i in o]
        return o


# %% ../nbs/00_core.ipynb #d18e1759
def EChart(options: dict, chart_id: str = None, width: str = "100%", height: str = "400px",
           theme: str = None, hx_get_click: str = None, hx_target_click: str = None,
           hx_click_vals: list = None, hx_click_cb: str = None):
    "Render an EChart with support for themes, JS formatters, HTMX click events, and memory cleanup."
    chart_id = chart_id or f"echart_{uuid.uuid4().hex}"

    # Use our custom encoder to handle JSFunc objects safely
    safe_options = json.dumps(options, cls=EChartsEncoder)

    # Optional theme logic (e.g., 'dark' or 'light')
    theme_str = f"'{theme}'" if theme else "null"

    # Optional HTMX click handler logic
    click_logic = ""
    if hx_get_click:
        target = f"target: '{hx_target_click}', " if hx_target_click else ""
        if hx_click_cb:
            vals_js = f"var vals = ({hx_click_cb})(params);"
        else:
            fields = hx_click_vals or ["name", "value", "seriesName"]
            fields_js = json.dumps(fields)
            vals_js = f"var vals = {{}}; {fields_js}.forEach(function(k) {{ vals[k] = params[k]; }});"
        click_logic = f"""
        myChart.on('click', function(params) {{
            {vals_js}
            htmx.ajax('GET', '{hx_get_click}', {{
                {target}
                values: vals
            }});
        }});
        """

    js_code = f"""
    (function() {{
        const dom = document.getElementById('{chart_id}');
        if (!dom) return;

        const myChart = echarts.init(dom, {theme_str});

        const option = {safe_options};
        function reviveJS(obj) {{
            for (let k in obj) {{
                if (typeof obj[k] === 'object' && obj[k] !== null) reviveJS(obj[k]);
                else if (typeof obj[k] === 'string' && obj[k].startsWith('!JS!')) {{
                    try {{ obj[k] = eval('(' + obj[k].slice(4) + ')'); }} catch(e) {{ console.error(e); }}
                }}
            }}
        }}
        reviveJS(option);

        myChart.setOption(option);

        {click_logic}

        const resizeObserver = new ResizeObserver(() => {{ myChart.resize(); }});
        resizeObserver.observe(dom);

        dom.addEventListener('htmx:beforeCleanupElement', function() {{
            resizeObserver.disconnect();
            myChart.dispose();
        }});
    }})();
    """

    return Div(
        Div(id=chart_id, style=f"width: {width}; height: {height};"),
        Script(NotStr(js_code))
    )

# %% ../nbs/00_core.ipynb #93b4461c
def preview_echart(echart, height="450px"):
    from IPython.display import HTML

    raw_html = to_xml(echart)
    srcdoc = f'<script src="https://cdn.jsdelivr.net/npm/echarts@{DEFAULT_ECHARTS_VERSION}/dist/echarts.min.js"></script>{raw_html}'
    srcdoc = srcdoc.replace("'", "&#39;")
    return HTML(f"<iframe srcdoc='{srcdoc}' style='width:100%;height:{height};border:none;'></iframe>")

# %% ../nbs/00_core.ipynb #7e1997cf
def EChartUpdate(chart_id: str, options: dict, merge: bool = True):
    "Update an existing chart instance. Set `merge=False` to replace all options instead of merging."
    safe_options = json.dumps(options, cls=EChartsEncoder)
    not_merge_str = "false" if merge else "true"

    js_code = f"""
    (function() {{
        const dom = document.getElementById('{chart_id}');
        if (!dom) return;
        const myChart = echarts.getInstanceByDom(dom);
        if (myChart) {{
            const option = {safe_options};
            function reviveJS(obj) {{
                for (let k in obj) {{
                    if (typeof obj[k] === 'object' && obj[k] !== null) reviveJS(obj[k]);
                    else if (typeof obj[k] === 'string' && obj[k].startsWith('!JS!')) {{
                        try {{ obj[k] = eval('(' + obj[k].slice(4) + ')'); }} catch(e) {{}}
                    }}
                }}
            }}
            reviveJS(option);
            myChart.setOption(option, {not_merge_str});
        }}
    }})();
    """
    return Script(NotStr(js_code))

# %% ../nbs/00_core.ipynb #9f9d5a9d
def EChartJS(chart_id: str, js_func: str):
    "Run a JS function against an existing chart instance. `js_func` receives `(chart, el)` as arguments."
    js_code = f"""
    (function() {{
        const el = document.getElementById('{chart_id}');
        if (!el) return;
        const chart = echarts.getInstanceByDom(el);
        if (!chart) return;
        ({js_func})(chart, el);
    }})();
    """
    return Script(NotStr(js_code))

# %% ../nbs/00_core.ipynb #1bcbc7bf
def EChartOOB(*scripts, sink_id: str = "script-sink"):
    "Wrap one or more EChart Script elements in an OOB-swappable Div for HTMX responses."
    return Div(*scripts, id=sink_id, hx_swap_oob="true")
