from __future__ import annotations

from typing import TYPE_CHECKING, Any

from core_10x.rc import RC, RC_TRUE
from core_10x.trait import T, Ui

from ui_10x.utils import UxStyleSheet, ux

if TYPE_CHECKING:
    from core_10x.trait import Trait


class TraitWidget:
    # __slots__ = ('program_edit', 'refresh_context', 'style_sheet', 'trait', 'trait_editor')
    s_hinted_widgets = {}

    def __init_subclass__(cls, widget_type: Ui.WIDGET_TYPE = None, **kwargs):
        if widget_type:
            TraitWidget.s_hinted_widgets[widget_type] = cls

    @staticmethod
    def widget_class(widget_type: Ui.WIDGET_TYPE):
        w_cls = TraitWidget.s_hinted_widgets.get(widget_type)
        assert w_cls, f'Unknown trait widget type: {widget_type}'
        return w_cls

    @staticmethod
    def instance(trait_editor) -> TraitWidget | None:
        w_type = trait_editor.ui_hint.widget_type
        if w_type is not Ui.WIDGET_TYPE.NONE:
            w_cls = TraitWidget.widget_class(w_type)
            return w_cls(trait_editor)
        return None

    def __init__(self, trait_editor):
        self.trait_editor = trait_editor
        self.trait: Trait = trait_editor.trait
        self.program_edit = False

        self._create()

        if self.trait_editor.is_read_only():
            self._set_read_only(True)

        self.set_tool_tip(self.trait.ui_hint.tip)

        traitable = self.trait_editor.traitable

        self.style_sheet = sheet = UxStyleSheet(self)
        sh = traitable.get_style_sheet(self.trait)
        sheet.update(sh)

        value = traitable.get_value(self.trait)
        self.set_widget_value(value)

        self.refresh_context = ctx = RefreshContext(self)
        traitable.create_ui_node(self.trait, ctx.emit_signal)

    def refresh(self):
        traitable = self.trait_editor.traitable
        trait = self.trait
        traitable.update_ui_node(self.trait)

        if not trait.flags_on(T.EXPENSIVE):
            value = traitable.get_value(trait)
            self.set_widget_value(value)
        else:
            sh = traitable.get_style_sheet(trait)
            self.style_sheet.update(sh)

            if not traitable.is_valid(trait):
                self.style_sheet.update({Ui.BG_COLOR: 'lightblue'}, _system=True)

    def widget_value(self):
        return self._value()

    def set_widget_value(self, value) -> bool:
        self.program_edit = True
        try:
            self._set_value(value)
        except Exception:  # -- the real widget is gone (so far, has encountered this only in Qt on Mac OS)
            # self.clean()   #-- TODO: fix!
            self.program_edit = False
            return False

        sh = self.trait_editor.traitable.get_style_sheet(self.trait)
        self.style_sheet.update(sh)
        self.program_edit = False
        return True

    def update_trait_value(self, value: Any = None, invalidate: bool = False):
        if not self.program_edit:
            traitable = self.trait_editor.traitable
            if invalidate:
                traitable.invalidate_value(self.trait)
                rc = RC_TRUE
            else:
                if value is None:
                    value = self.widget_value()

                try:
                    rc = traitable.set_value(self.trait, value)
                except Exception as e:
                    rc = RC(False, str(e))

            if not rc:
                self.set_tool_tip(rc.error())
                self.style_sheet.update({Ui.BG_COLOR: 'orange'}, _system=True)
            else:
                self.set_tool_tip(self.trait.ui_hint.tip)
                self.style_sheet.restore()

    # fmt: off
    def _create(self):              raise NotImplementedError
    def _set_read_only(self, flag): raise NotImplementedError
    def _value(self):               raise NotImplementedError
    def _set_value(self, value):    raise NotImplementedError
    # fmt: on


class RefreshContext(ux.Object):
    REFRESH = ux.signal_decl(object)

    def __init__(self, trait_widget: TraitWidget):
        super().__init__()
        self.trait_widget = trait_widget
        self.REFRESH.connect(TraitWidget.refresh, type=ux.QueuedConnection)

    def emit_signal(self):
        self.REFRESH.emit(self.trait_widget)
