import param
import pandas as pd
from typing import List
import panel as pn


class DataCombiner(param.Parameterized):

    datas: List[pd.DataFrame] = param.List(
        default=[], item_type=pd.DataFrame, allow_refs=True, nested_refs=True
    )
    index_columns = param.List(default=[], item_type=str)
    merged_data: pd.DataFrame = param.DataFrame()

    highlight_series = param.Series(default=None)
    highlight_name = param.ObjectSelector(default=None, objects=[], allow_None=True)
    highlight_index = param.ObjectSelector(default=None, objects=[], allow_None=True)

    highlight_ranges = param.Dict(default={})

    def __init__(self, **params):
        super().__init__(**params)
        self._dynamic_range_params: set[str] = set()
        self.param.watch(self.merge, ["datas", "index_columns"])
        self.param.watch(self.set_highlight_name_objects, ["datas", "index_columns"])
        self.param.watch(self.set_highlight_index_objects, ["index_columns"])
        self.param.watch(
            self.set_highlight_ranges,
            ["highlight_index", "index_columns", "merged_data"],
        )
        self.param.watch(
            self.set_highlight_series,
            ["highlight_index", "highlight_ranges", "highlight_name", "merged_data"],
        )
        self.param.watch(self.sync_highlight_range_params, ["highlight_ranges"])

        self._range_widgets = pn.Column()
        self.set_highlight_index_objects(None)
        self.view = self.make_view()

    def set_highlight_series(self, event):
        if (
            self.merged_data is None
            or self.highlight_index is None
            or self.highlight_name is None
        ):
            return
        x = (
            self.merged_data.loc[
                (
                    self.merged_data["start_datetime"]
                    >= self.highlight_ranges["start_datetime"][0]
                )
                & (
                    self.merged_data["start_datetime"]
                    <= self.highlight_ranges["start_datetime"][1]
                )
            ]
            .groupby(self.highlight_index)[self.highlight_name]
            .mean()
        )

        self.param.update(highlight_series=x)

    def sync_highlight_range_params(self, event=None):
        current_keys = set(self.highlight_ranges.keys())

        for name in list(self._dynamic_range_params - current_keys):
            delattr(type(self), name)
            self._dynamic_range_params.discard(name)

        for name, value in self.highlight_ranges.items():
            sanitized = tuple(None if pd.isna(v) else v for v in value)
            if name not in self._dynamic_range_params:
                if pd.api.types.is_datetime64_any_dtype(self.merged_data[name]):
                    p = param.DateRange(default=None, bounds=sanitized)
                else:
                    p = param.Range(default=sanitized if all(v is not None for v in sanitized) else None, bounds=sanitized)
                self.param.add_parameter(name, p)
                self._dynamic_range_params.add(name)
            else:
                self.param[name].bounds = sanitized
                self.param.update(**{name: sanitized if all(v is not None for v in sanitized) else None})

        self._range_widgets.objects = [self.param[name] for name in self._dynamic_range_params]

    def set_highlight_ranges(self, event):
        if self.merged_data is None or self.highlight_index is None:
            return
        dynamic_indexers = set(self.index_columns)
        dynamic_indexers.remove(self.highlight_index)
        _ranges = dict()
        for idxr in dynamic_indexers:
            _ranges[idxr] = (self.merged_data[idxr].min(), self.merged_data[idxr].max())
        self.param.update(highlight_ranges=_ranges)

    def set_highlight_name_objects(self, event):
        if len(self.datas) > 0:
            columns = set(self.merged_data.columns)
            options = list(columns.difference(set(self.index_columns)))
            self.param["highlight_name"].objects = options
            if self.highlight_name == None:
                self.param.update(
                    highlight_name=options[0] if len(options) > 0 else None
                )

    def set_highlight_index_objects(self, event):
        self.param["highlight_index"].objects = self.index_columns
        if self.highlight_index == None:
            self.param.update(
                highlight_index=(
                    self.index_columns[0] if len(self.index_columns) > 0 else None
                )
            )

    def merge(self, event):
        left = self.datas[0].set_index(self.index_columns)
        for right in self.datas[1:]:
            right = right.set_index(self.index_columns)
            left = left.join(right, how="outer")
        self.param.update(merged_data=left.reset_index())

    def make_view(self):
        view = pn.Column(
            self.param["highlight_name"],
            # self.param["highlight_index"],
            self._range_widgets,
        )
        return view
