import uuid

from dash import html, dcc, callback, Input, Output, ALL, ctx, State
from ssb_dash_components import Accordion, Checkbox

import polars as pl

from .loading_test import DatasetConfig

class SeriesSelector(html.Div):
    """
    The class handles its own global state. 
    This is the state that is passed to components downstream
    """
    class ids:
        store = lambda aio_id: {
            "component": "SeriesSelector",
            "subcomponent": "store",
            "aio_id": aio_id,
        }
        selected_store = lambda aio_id: {
            "component": "SeriesSelector",
            "subcomponent": "selected_store",
            "aio_id": aio_id,
        }
        selector_container = lambda aio_id: {
            "component": "SeriesSelector",
            "subcomponent": "selector-container",
            "aio_id": aio_id,
        }
        checkbox = lambda aio_id, path, col, dataset: {
            "component": "SeriesSelector",
            "subcomponent": "checkbox",
            "path": path,
            "aio_id": aio_id,
            "col": col,
            "dataset": dataset,
        }

    def __init__(self, datasets: dict[str, DatasetConfig], aio_id: None | str = None):
        """
        Expects the datasets.
        Can provide an aio_id if necessary.
        """
        if aio_id is None:
            aio_id = str(uuid.uuid4())
        super().__init__(
            children=[
                dcc.Store(data=[], id=self.ids.store(aio_id)),
                dcc.Store(data=[], id=self.ids.selected_store(aio_id)),
                html.Div(id=self.ids.selector_container(aio_id)),
            ]
        )

        @callback(
            Output(self.ids.selector_container(aio_id), "children"),
            Input(self.ids.store(aio_id), "data"),
            State(self.ids.selected_store(aio_id), "data"),
        )
        def update_selector_view(selected: list[dict], current_state: list[dict]):
            # Renders the new series selector when selected files change
            # Uses stored state so users dont lose settings already entered
            children: list[Accordion] = []

            for item in selected:
                path: str | None = item.get("path")
                dataset: str | None = item.get("dataset")
                if path is None:
                    continue

                schema = pl.read_parquet_schema(path)
                cols: list[Checkbox] = []
                for col, col_type in schema.items():
                    if col_type.is_numeric():
                        box = Checkbox(
                            label=col,
                            name=col,
                            value=any(
                                (i.get("path") == path)
                                and (i.get("col") == col)
                                and (i.get("dataset") == dataset)
                                for i in current_state
                            ),
                            id=self.ids.checkbox(aio_id, path, col, dataset),
                        )
                        cols.append(box)

                children.append(Accordion(header=path, children=cols))
            return children

        @callback(
            Output(self.ids.selected_store(aio_id), "data"),
            Input(self.ids.checkbox(aio_id, ALL, ALL, ALL), "value"),
            Input(self.ids.checkbox(aio_id, ALL, ALL, ALL), "id"),
            State(self.ids.selected_store(aio_id), "data"),
            State(self.ids.store(aio_id), "data"),
        )
        def update_checked(
            checked: list[str],
            ids: list[dict[str, str]],
            current_state: list[dict[str, str]],
            files: list[dict[str, str]],
        ):
            # Reacts to inputs in settings and stores them in a store so the user
            # doesn't lose settings when they add another series.
            triggered = ctx.triggered_id

            # We need to drop files that have been removed up stream
            # This ensures that when a user unselects a file it is removed from the view and
            # all hidden states
            active_files = {file["path"] for file in files}
            active_state = {file["path"] for file in current_state}
            files_to_keep = active_state.intersection(active_files)
            current_state = [
                file for file in current_state if file["path"] in files_to_keep
            ]

            # A callback is triggered when checkboxes are first rendered
            # This is not an interaction and the following code makes sure
            # we ignore that callback
            if any(".id" in key for key in ctx.triggered_prop_ids.keys()):
                return current_state

            if isinstance(triggered, dict):
                col: str | None = triggered.get("col")
                path: str | None = triggered.get("path")
                dataset: str | None = triggered.get("dataset")
                if col is None:
                    return current_state

                if path is None:
                    return current_state

                if dataset is None:
                    return current_state

                for id, item in zip(ids, checked):
                    if ctx.triggered_id == id:
                        if item != True:
                            current_state = [
                                i
                                for i in current_state
                                if (i.get("path") != path)
                                or (i.get("col") != col)
                                or (i.get("dataset") != dataset)
                            ]
                            break
                        else:
                            current_state.append(
                                {"path": path, "col": col, "dataset": dataset}
                            )
                            break

            return current_state
