import plotly.express as px
import plotly.graph_objects as go
from typing import Union, List, Optional
import pandas as pd


class PlotlyChartBuilder:

    def _normalize(self, y_axes: Union[str, List[str]]):
        return [y_axes] if isinstance(y_axes, str) else y_axes

    def bar(self, df: pd.DataFrame, x_axis: str, y_axes,
            title=None, x_axis_title=None, y_axis_title=None, theme=None):

        y_axes = self._normalize(y_axes)

        fig = px.bar(df, x=x_axis, y=y_axes, template=theme)

        fig.update_layout(
            title=title,
            xaxis_title=x_axis_title or x_axis,
            yaxis_title=y_axis_title or ", ".join(y_axes)
        )

        return fig

    def line(self, df, x_axis, y_axes,
             title=None, x_axis_title=None, y_axis_title=None, theme=None):

        y_axes = self._normalize(y_axes)

        fig = px.line(df, x=x_axis, y=y_axes, template=theme)

        fig.update_layout(
            title=title,
            xaxis_title=x_axis_title or x_axis,
            yaxis_title=y_axis_title or ", ".join(y_axes)
        )

        return fig

    def area(self, df, x_axis, y_axes,
             title=None, x_axis_title=None, y_axis_title=None, theme=None):

        y_axes = self._normalize(y_axes)

        fig = px.area(df, x=x_axis, y=y_axes, template=theme)

        fig.update_layout(
            title=title,
            xaxis_title=x_axis_title or x_axis,
            yaxis_title=y_axis_title or ", ".join(y_axes)
        )

        return fig

    def pie(self, df, names, values,
            title=None, x_axis_title=None, y_axis_title=None, theme=None):

        fig = px.pie(df, names=names, values=values, template=theme)

        fig.update_layout(title=title)

        return fig

    def histogram(self, df, column, bins=20,
                  title=None, x_axis_title=None, y_axis_title=None, theme=None):

        fig = px.histogram(df, x=column, nbins=bins, template=theme)

        fig.update_layout(
            title=title,
            xaxis_title=x_axis_title or column,
            yaxis_title=y_axis_title or "Count"
        )

        return fig

    def combo(self, df, x_axis, y1, y2,
              label1=None, label2=None,
              title=None, theme=None):

        fig = go.Figure()

        fig.add_trace(go.Bar(
            x=df[x_axis],
            y=df[y1],
            name=label1 or y1
        ))

        fig.add_trace(go.Scatter(
            x=df[x_axis],
            y=df[y2],
            name=label2 or y2,
            yaxis="y2",
            mode="lines"
        ))

        fig.update_layout(
            title=title,
            template=theme,
            xaxis_title=x_axis,
            yaxis_title=label1 or y1,
            yaxis2=dict(
                title=label2 or y2,
                overlaying="y",
                side="right"
            )
        )

        return fig
    
    def kpi(self, value, title=None, theme=None):

        fig = go.Figure(go.Indicator(
            mode="number",
            value=value,
            title={"text": title}
        ))

        fig.update_layout(template=theme)
        return fig


    def kpi_compare(self, current, previous, change_pct,
                    title=None, theme=None):

        fig = go.Figure(go.Indicator(
            mode="number+delta",
            value=current,
            delta={
                "reference": previous,
                "relative": True
            },
            title={"text": title}
        ))

        fig.update_layout(template=theme)
        return fig