import matplotlib.pyplot as plt
import json
import tempfile
import webbrowser
from matplotlib.collections import PolyCollection
try:
    from IPython.display import display, HTML
    _HAS_IPYTHON = True
except ImportError:
    _HAS_IPYTHON = False

class Ptoe:
    def __init__(self, plt_module):
        self._plt = plt_module

    def __getattr__(self, name):
        return getattr(self._plt, name)

    def rgba_to_echarts(self, color):
        if isinstance(color, str):
            return color

    def to_json_safe(self, obj):
        if isinstance(obj, dict):
            return {k: self.to_json_safe(v) for k, v in obj.items()}

        if isinstance(obj, list):
            return [self.to_json_safe(v) for v in obj]

        if isinstance(obj, tuple):
            return [self.to_json_safe(v) for v in obj]

        try:
            import numpy as np
            if isinstance(obj, (np.integer,)):
                return int(obj)
            if isinstance(obj, (np.floating,)):
                return float(obj)
        except ImportError:
            pass

        return obj


        if isinstance(color, (list, tuple)) and len(color) >= 3:
            r = int(color[0] * 255)
            g = int(color[1] * 255)
            b = int(color[2] * 255)
            a = color[3] if len(color) > 3 else 1.0
            return f"rgba({r},{g},{b},{a})"

        return None

    def to_py(self, v):
        if hasattr(v, "item"):
            return v.item()
        return v


    def render_html(self, option):
        safe_option = self.to_json_safe(option)
        html = f"""
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8" />
    <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
    </head>
    <body>
    <div id="chart" style="width: 100%; height: 600px;"></div>
    <script>
        const chart = echarts.init(document.getElementById('chart'));
        const option = {json.dumps(safe_option, ensure_ascii=False)};
        chart.setOption(option);
    </script>
    </body>
    </html>
    """
        with tempfile.NamedTemporaryFile("w", suffix=".html", delete=False, encoding="utf-8") as f:
            f.write(html)
            webbrowser.open(f.name)

    def render_inline(self, option):
        if not _HAS_IPYTHON:
            raise RuntimeError(
                "IPython is not available. "
                "Use inline=True only in Jupyter/Colab."
            )

        html = f"""
        <div id="chart" style="width: 100%; height: 500px;"></div>
        <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
        <script>
        const chart = echarts.init(document.getElementById('chart'));
        const option = {json.dumps(option, ensure_ascii=False)};
        chart.setOption(option);
        </script>
        """
        display(HTML(html))


    def detect_chart_type(self, ax):
        if ax.containers:
            return "bar"
        if ax.lines:
            return "line"
        if any(isinstance(c, PolyCollection) for c in ax.collections):
            return "area"
        if ax.collections:
            return "scatter"
        raise NotImplementedError("unsupported chart type")


    def extract_bar(self, ax):
        labels = [tick.get_text() for tick in ax.get_xticklabels()]
        _, legend_labels = ax.get_legend_handles_labels()

        series = []

        is_stacked = len(ax.containers) > 1  # 핵심

        for idx, container in enumerate(ax.containers):
            values = []

            color = None
            if len(container) > 0:
                color = self.rgba_to_echarts(container[0].get_facecolor())

            name = None
            if idx < len(legend_labels):
                name = legend_labels[idx]

            for rect in container:
                values.append(self.to_py(rect.get_height()))

            series.append({
                "type": "bar",
                "name": name,
                "data": values,
                "color": color,
                "x_labels": labels,
                "stack": "total" if is_stacked else None,
            })

        return series



    def extract_line(self, ax):
        series = []
        for line in ax.lines:
            x = list(line.get_xdata())
            y = list(line.get_ydata())
            color = self.rgba_to_echarts(line.get_color())
            label = line.get_label()

            drawstyle = line.get_drawstyle()
            step = None
            if drawstyle.startswith("steps"):
                if drawstyle == "steps-pre":
                    step = "start"
                elif drawstyle == "steps-mid":
                    step = "middle"
                elif drawstyle == "steps-post":
                    step = "end"

            item = {
                "type": "line",
                "name": label if not label.startswith("_") else None,
                "data": [
                    [self.to_py(xi), self.to_py(yi)]
                    for xi, yi in zip(x, y)
                ],
                "color": color,
            }

            if step:
                item["step"] = step

            series.append(item)

        return series



    def extract_scatter(self, ax):
        series = []
        for col in ax.collections:
            offsets = col.get_offsets()
            xs = offsets[:, 0].tolist()
            ys = offsets[:, 1].tolist()

            facecolors = col.get_facecolors()
            color = (
                self.rgba_to_echarts(tuple(facecolors[0]))
                if len(facecolors) > 0 else None
            )

            label = col.get_label()

            series.append({
                "type": "scatter",
                "name": label if not label.startswith("_") else None,
                "data": list(zip(xs, ys)),
                "color": color,
            })
        return series

    def extract_area(self, ax):
        series = []

        for col in ax.collections:
            if not hasattr(col, "get_paths"):
                continue

            paths = col.get_paths()
            if not paths:
                continue

            verts = paths[0].vertices
            x = verts[:, 0].tolist()
            y = verts[:, 1].tolist()

            color = None
            facecolors = col.get_facecolors()
            if len(facecolors) > 0:
                color = self.rgba_to_echarts(tuple(facecolors[0]))

            series.append({
                "type": "line",
                "name": None,
                "data": [
                    [self.to_py(xi), self.to_py(yi)]
                    for xi, yi in zip(xs, ys)
                ],
                "color": color,
                "areaStyle": {},
            })

        return series


    def build_echarts_option(
        self,
        chart_type,
        title,
        xlabel,
        ylabel,
        x_labels,
        series_data,
        legend_labels,
    ):
        xaxis_type = (
            "category"
            if chart_type == "bar"
            else self.infer_xaxis_type(series_data)
        )

        option = {
            "title": {"text": title} if title else {},
            "tooltip": {"trigger": "axis"},
            "legend": {"data": legend_labels} if legend_labels else {},
            "xAxis": {
                "type": xaxis_type,
                "name": xlabel,
                "data": x_labels if xaxis_type == "category" else None,
            },
            "yAxis": {
                "type": "value",
                "name": ylabel,
            },
            "series": [],
        }

        for s in series_data:
            series_item = {
                "type": s["type"],
                "name": s.get("name"),
                "data": s["data"],
            }

            if s.get("color"):
                series_item["itemStyle"] = {"color": s["color"]}

            if s.get("stack"):
                series_item["stack"] = s["stack"]

            option["series"].append(series_item)

        return option

    def infer_xaxis_type(self, series_data):
        if not series_data:
            return "value"

        data = series_data[0].get("data", [])
        if not data:
            return "value"

        first = data[0]
        if isinstance(first, (list, tuple)):
            x = first[0]
        else:
            x = first

        return "category" if isinstance(x, str) else "value"
        

    def show(self, inline=False):
        fig = self._plt.gcf()
        if len(fig.axes) != 1:
            raise NotImplementedError("only single axes supported")

        ax = fig.axes[0]

        title = ax.get_title()
        xlabel = ax.get_xlabel()
        ylabel = ax.get_ylabel()
        _, legend_labels = ax.get_legend_handles_labels()

        chart_type = self.detect_chart_type(ax)

        if chart_type == "bar":
            data = self.extract_bar(ax)    
        elif chart_type == "line":
            data = self.extract_line(ax)
        elif chart_type == "scatter":
            data = self.extract_scatter(ax)
        elif chart_type == "area":
            data = self.extract_area(ax)



        option = self.build_echarts_option(
            chart_type=chart_type,
            title=title,
            xlabel=xlabel,
            ylabel=ylabel,
            x_labels=data[0].get("x_labels"),
            series_data=data,
            legend_labels=legend_labels,
        )

        if inline:
            self.render_inline(option)
        else:
            self.render_html(option)

        self._plt.close(fig)

