from typing import TYPE_CHECKING, Optional, Union
from pandas import DataFrame

from copy import deepcopy

from shimoku.plt.EChart_definitions.default_echart_options import (
    get_common_echart_options,
    get_common_series_options,
)

if TYPE_CHECKING:
    from shimoku.plt.plt_layer import PlotLayer


def thin_variant(y: list[str], series_options: dict):
    len_y = len(y)
    dot_series_options = deepcopy(series_options)
    series_options["barWidth"] = 3
    series_options["barGap"] = "-100%"
    series_options["label"] = {"show": False}
    if len_y > 1:
        series_options["itemStyle"]["opacity"] = 0.75
    series_options = len_y * [series_options]
    dot_series_options["type"] = "scatter"
    dot_series_options["itemStyle"]["opacity"] = 1
    dot_series_options["itemStyle"]["borderWidth"] = 0
    dot_series_options["label"] = {"position": "top", "color": "inherit"}
    series_options.append(dot_series_options)
    y = y + y
    return y, series_options


def shadow_variant(series_options: dict):
    series_options["showBackground"] = True
    series_options["backgroundStyle"] = {"color": "rgba(180, 180, 180, 0.2)"}
    series_options["label"] = {"position": "top", "color": "inherit"}
    return series_options


def horizontal_clean_variant(common_options: dict):
    common_options["xAxis"][0].update(
        {
            "position": "top",
            "axisLabel": {
                "rotate": -45,
            },
        }
    )
    return common_options


def interpret_variant(variant: Optional[str]) -> tuple[Optional[str], Optional[str]]:
    if not variant:
        return None, None
    variant = variant.split(" ")
    if len(variant) == 1:
        if variant[0] in ["thin", "shadow"]:
            return None, variant[0]
        elif variant[0] in ["clean", "minimal"]:
            return variant[0], None
        raise ValueError(f"Invalid variant: {variant}")
    elif len(variant) == 2 and variant[0] in ["clean", "minimal"]:
        return variant[0], variant[1]
    else:
        raise ValueError(f"Invalid variant: {variant}")


async def bar_chart(
    self: "PlotLayer",
    data: Union[str, DataFrame, list[dict]],
    order: int,
    x: str,
    y: Union[list[str], str, None] = None,
    rows_size: Optional[int] = None,
    cols_size: Optional[int] = None,
    padding: Optional[str] = None,
    title: Optional[str] = None,
    x_axis_name: Optional[str] = None,
    y_axis_name: Optional[str] = None,
    show_values: Union[list[str], str, None] = None,
    option_modifications: Optional[dict] = None,
    variant: Optional[str] = None,
):
    """Create a bar chart"""
    series_options = get_common_series_options()
    data_mapping_to_tuples = await self._choose_data(order, data=data)
    series_options["type"] = "bar"
    clean, variant = interpret_variant(variant)

    if variant == "thin":
        y = y if y else [k for k in data_mapping_to_tuples.keys() if k != x]
        y, series_options = thin_variant(y, series_options)
    elif variant == "shadow":
        series_options = shadow_variant(series_options)

    await self._create_trend_chart(
        echart_options=get_common_echart_options(),
        variant=clean,
        series_options=series_options,
        option_modifications=option_modifications,
        axes=x,
        values=y,
        data_mapping_to_tuples=data_mapping_to_tuples,
        order=order,
        x_axis_names=x_axis_name,
        y_axis_names=y_axis_name,
        title=title,
        rows_size=rows_size,
        cols_size=cols_size,
        padding=padding,
        show_values=show_values,
    )


async def stacked_bar_chart(
    self: "PlotLayer",
    data: Union[str, DataFrame, list[dict]],
    order: int,
    x: str,
    y: Union[list[str], str, None] = None,
    rows_size: Optional[int] = None,
    cols_size: Optional[int] = None,
    padding: Optional[str] = None,
    title: Optional[str] = None,
    x_axis_name: Optional[str] = None,
    y_axis_name: Optional[str] = None,
    show_values: Union[list[str], str, None] = None,
    option_modifications: Optional[dict] = None,
    variant: Optional[str] = None,
):
    """Create a stacked bar chart"""
    series_options = get_common_series_options()
    series_options.update(
        {
            "type": "bar",
            "areaStyle": {},
            "stack": "stack",
            "itemStyle": {"borderRadius": [0, 0, 0, 0]},
        }
    )
    data_mapping_to_tuples = await self._choose_data(order, data=data)
    clean, variant = interpret_variant(variant)
    if variant == "thin":
        y = y if y else [k for k in data_mapping_to_tuples.keys() if k != x]
        len_y = len(y)
        y, series_options = thin_variant(y, series_options)
        series_options[-1]["stack"] = "dot_stack"
        series_options[-1]["symbolSize"] = 5
        series_options[-1]["label"]["position"] = "left"
        series_options = (
            series_options[:len_y]
            + [series_options[-1]] * (len_y - 1)
            + [deepcopy(series_options[-1])]
        )
        series_options[-1]["label"]["position"] = "top"
        del series_options[-1]["symbolSize"]
    else:
        len_y = len(y) if y else len(data_mapping_to_tuples.keys()) - 1
        if len_y > 1:
            series_options = [series_options] * (len_y - 1)
            series_options.append(deepcopy(series_options[-1]))
        else:
            series_options = [series_options]
        if variant == "shadow":
            series_options[-1] = shadow_variant(series_options[-1])
        series_options[-1]["itemStyle"]["borderRadius"] = [9, 9, 0, 0]

    await self._create_trend_chart(
        echart_options=get_common_echart_options(),
        variant=clean,
        axes=x,
        values=y,
        data_mapping_to_tuples=data_mapping_to_tuples,
        series_options=series_options,
        option_modifications=option_modifications,
        order=order,
        x_axis_names=x_axis_name,
        y_axis_names=y_axis_name,
        title=title,
        rows_size=rows_size,
        cols_size=cols_size,
        padding=padding,
        show_values=show_values,
    )


async def zero_centered_bar_chart(
    self: "PlotLayer",
    data: Union[str, DataFrame, list[dict]],
    order: int,
    x: str,
    y: Union[list[str], str, None] = None,
    rows_size: Optional[int] = None,
    cols_size: Optional[int] = None,
    padding: Optional[str] = None,
    title: Optional[str] = None,
    x_axis_name: Optional[str] = None,
    y_axis_name: Optional[str] = None,
    show_values: Union[list[str], str, None] = None,
    option_modifications: Optional[dict] = None,
    variant: Optional[str] = None,
):
    """Create a zero centered bar chart"""
    common_options = get_common_echart_options()
    series_options = get_common_series_options()
    data_mapping_to_tuples = await self._choose_data(order, data=data)
    series_options["type"] = "bar"
    common_options["xAxis"][0]["type"] = "value"
    series_options["itemStyle"]["borderRadius"] = [0, 0, 0, 0]
    del common_options["xAxis"][0]["data"]
    common_options["yAxis"][0].update(
        {
            "type": "category",
            "data": "#set_data#",
        }
    )
    clean, variant = interpret_variant(variant)
    if variant == "thin":
        y = y if y else [k for k in data_mapping_to_tuples.keys() if k != x]
        y, series_options = thin_variant(y, series_options)
    elif variant == "shadow":
        series_options = shadow_variant(series_options)

    await self._create_trend_chart(
        echart_options=common_options
        if not clean
        else horizontal_clean_variant(common_options),
        series_options=series_options,
        variant=clean,
        option_modifications=option_modifications,
        axes=x,
        values=y,
        data_mapping_to_tuples=data_mapping_to_tuples,
        order=order,
        x_axis_names=x_axis_name,
        y_axis_names=y_axis_name,
        title=title,
        rows_size=rows_size,
        cols_size=cols_size,
        padding=padding,
        show_values=show_values,
    )


async def horizontal_bar_chart(
    self: "PlotLayer",
    data: Union[str, DataFrame, list[dict]],
    order: int,
    x: str,
    y: Union[list[str], str, None] = None,
    rows_size: Optional[int] = None,
    cols_size: Optional[int] = None,
    padding: Optional[str] = None,
    title: Optional[str] = None,
    x_axis_name: Optional[str] = None,
    y_axis_name: Optional[str] = None,
    show_values: Union[list[str], str, None] = None,
    option_modifications: Optional[dict] = None,
    variant: Optional[str] = None,
):
    """Create a horizontal bar chart"""
    common_options = get_common_echart_options()
    series_options = get_common_series_options()
    data_mapping_to_tuples = await self._choose_data(order, data=data)
    series_options["type"] = "bar"
    common_options["xAxis"][0]["type"] = "value"
    series_options["itemStyle"]["borderRadius"] = [0, 9, 9, 0]
    del common_options["xAxis"][0]["data"]
    common_options["yAxis"][0].update(
        {
            "type": "category",
            "data": "#set_data#",
        }
    )
    clean, variant = interpret_variant(variant)
    if variant == "thin":
        y = y if y else [k for k in data_mapping_to_tuples.keys() if k != x]
        y, series_options = thin_variant(y, series_options)
        series_options[-1]["label"]["position"] = "right"
    elif variant == "shadow":
        series_options = shadow_variant(series_options)
        series_options["label"]["position"] = "right"

    await self._create_trend_chart(
        echart_options=common_options
        if not clean
        else horizontal_clean_variant(common_options),
        series_options=series_options,
        variant=clean,
        option_modifications=option_modifications,
        axes=x,
        values=y,
        data_mapping_to_tuples=data_mapping_to_tuples,
        order=order,
        x_axis_names=x_axis_name,
        y_axis_names=y_axis_name,
        title=title,
        rows_size=rows_size,
        cols_size=cols_size,
        padding=padding,
        show_values=show_values,
    )


async def stacked_horizontal_bar_chart(
    self: "PlotLayer",
    data: Union[str, DataFrame, list[dict]],
    order: int,
    x: str,
    y: Union[list[str], str, None] = None,
    rows_size: Optional[int] = None,
    cols_size: Optional[int] = None,
    padding: Optional[str] = None,
    title: Optional[str] = None,
    x_axis_name: Optional[str] = None,
    y_axis_name: Optional[str] = None,
    show_values: Union[list[str], str, None] = None,
    option_modifications: Optional[dict] = None,
    variant: Optional[str] = None,
):
    """Create a stacked horizontal bar chart"""
    common_options = get_common_echart_options()
    series_options = get_common_series_options()
    series_options.update(
        {
            "type": "bar",
            "areaStyle": {},
            "stack": "stack",
            "itemStyle": {"borderRadius": [0, 0, 0, 0]},
        }
    )
    data_mapping_to_tuples = await self._choose_data(order, data=data)
    clean, variant = interpret_variant(variant)
    if variant == "thin":
        y = y if y else [k for k in data_mapping_to_tuples.keys() if k != x]
        len_y = len(y)
        y, series_options = thin_variant(y, series_options)
        series_options[-1]["stack"] = "dot_stack"
        series_options[-1]["symbolSize"] = 5
        series_options[-1]["label"]["position"] = "top"
        series_options = (
            series_options[:len_y]
            + [series_options[-1]] * (len_y - 1)
            + [deepcopy(series_options[-1])]
        )
        series_options[-1]["label"]["position"] = "right"
        del series_options[-1]["symbolSize"]
    else:
        len_y = len(y) if y else len(data_mapping_to_tuples.keys()) - 1
        series_options = [series_options] * (len_y - 1)
        series_options.append(deepcopy(series_options[-1]))
        series_options[-1]["itemStyle"]["borderRadius"] = [0, 9, 9, 0]
        if variant == "shadow":
            series_options[-1] = shadow_variant(series_options[-1])
            series_options[-1]["label"]["position"] = "right"

    common_options["xAxis"][0]["type"] = "value"
    del common_options["xAxis"][0]["data"]
    common_options["yAxis"][0].update(
        {
            "type": "category",
            "data": "#set_data#",
        }
    )

    await self._create_trend_chart(
        echart_options=common_options
        if not clean
        else horizontal_clean_variant(common_options),
        variant=clean,
        series_options=series_options,
        option_modifications=option_modifications,
        axes=x,
        values=y,
        data_mapping_to_tuples=data_mapping_to_tuples,
        order=order,
        x_axis_names=x_axis_name,
        y_axis_names=y_axis_name,
        title=title,
        rows_size=rows_size,
        cols_size=cols_size,
        padding=padding,
        show_values=show_values,
    )
