from __future__ import annotations

from typing import Any, Callable

from ui.style import add_css_class
from ui.view_model import TabVM

_BADGE_LEVELS = ("warn", "err")


def _load_gtk_modules() -> Any:
    try:
        import gi

        gi.require_version("Gtk", "3.0")
        from gi.repository import Gtk

        return Gtk
    except Exception:
        return _FallbackGtk


class _FallbackWidget:
    def __init__(self, text: str = "") -> None:
        self.text = text
        self.label = text
        self.visible = True
        self.sensitive = True
        self.hexpand = False
        self.children: list[object] = []

    def set_text(self, text: str) -> None:
        self.text = text
        self.label = text

    def get_text(self) -> str:
        return self.text

    def set_visible(self, visible: bool) -> None:
        self.visible = visible

    def get_visible(self) -> bool:
        return self.visible

    def set_sensitive(self, sensitive: bool) -> None:
        self.sensitive = sensitive

    def set_hexpand(self, hexpand: bool) -> None:
        self.hexpand = bool(hexpand)

    def add(self, child: object) -> None:
        self.children.append(child)

    def pack_start(self, child: object, *_args: object) -> None:
        self.children.append(child)


class _FallbackLabel(_FallbackWidget):
    pass


class _FallbackButton(_FallbackWidget):
    def __init__(self, label: str = "") -> None:
        super().__init__(label)
        self._callback: Callable[..., Any] | None = None

    def connect(self, signal: str, callback: Callable[..., Any]) -> None:
        if signal == "clicked":
            self._callback = callback

    def emit(self, signal: str) -> None:
        if signal == "clicked" and self.sensitive and self._callback is not None:
            self._callback(self)


class _FallbackBox(_FallbackWidget):
    def __init__(self, _orientation: int | None = None, _spacing: int = 0) -> None:
        super().__init__(text="")
        self.orientation = _orientation
        self.spacing = _spacing


class _FallbackGtk:
    class Orientation:
        VERTICAL = 1
        HORIZONTAL = 2

    Label = _FallbackLabel
    Box = _FallbackBox
    Button = _FallbackButton


def _remove_css_class(widget: Any, name: str) -> None:
    try:
        widget.get_style_context().remove_class(name)
        return
    except Exception:
        pass
    classes = getattr(widget, "css_classes", None)
    if isinstance(classes, list) and name in classes:
        classes.remove(name)


class TabBar:
    def __init__(
        self,
        tabs: tuple[TabVM, ...],
        active: str,
        on_select: Callable[[str], None],
        gtk_module: Any | None = None,
    ) -> None:
        self.gtk = gtk_module if gtk_module is not None else _load_gtk_modules()
        self._on_select = on_select
        self.active = active
        self.buttons: dict[str, Any] = {}
        self.title_labels: dict[str, Any] = {}
        self.badges: dict[str, Any] = {}
        self._tab_ids: tuple[str, ...] = ()
        horizontal = getattr(getattr(self.gtk, "Orientation", None), "HORIZONTAL", 0)
        self.widget = self._new_box(self.gtk, horizontal, spacing=0)
        add_css_class(self.widget, "tab-strip")
        self._build_tabs(tabs)

    @staticmethod
    def _new_box(gtk: Any, orientation: int = 0, spacing: int = 0) -> Any:
        try:
            return gtk.Box(orientation=orientation, spacing=spacing)
        except TypeError:
            return gtk.Box(orientation, spacing)

    @staticmethod
    def _new_label(gtk: Any, text: str) -> Any:
        try:
            return gtk.Label(label=text)
        except TypeError:
            return gtk.Label(text)

    @staticmethod
    def _new_button(gtk: Any) -> Any:
        try:
            return gtk.Button()
        except TypeError:
            return gtk.Button("")

    @staticmethod
    def _pack(parent: Any, child: Any, *, expand: bool = False) -> None:
        if hasattr(parent, "pack_start"):
            parent.pack_start(child, expand, expand, 0)
        else:
            parent.add(child)

    @staticmethod
    def _remove_child(parent: Any, child: Any) -> None:
        remove = getattr(parent, "remove", None)
        if callable(remove):
            try:
                remove(child)
                return
            except Exception:
                pass
        children = getattr(parent, "children", None)
        if isinstance(children, list) and child in children:
            children.remove(child)

    def _build_tabs(self, tabs: tuple[TabVM, ...]) -> None:
        for button in self.buttons.values():
            self._remove_child(self.widget, button)
        self.buttons.clear()
        self.title_labels.clear()
        self.badges.clear()
        horizontal = getattr(getattr(self.gtk, "Orientation", None), "HORIZONTAL", 0)
        for tab in tabs:
            button = self._new_button(self.gtk)
            add_css_class(button, "tab")
            if hasattr(button, "set_hexpand"):
                button.set_hexpand(True)
            row = self._new_box(self.gtk, horizontal, spacing=4)
            title = self._new_label(self.gtk, tab.label)
            badge = self._new_label(self.gtk, str(tab.badge_count))
            add_css_class(badge, "tab-badge")
            if hasattr(badge, "set_no_show_all"):
                badge.set_no_show_all(True)
            self._pack(row, title)
            self._pack(row, badge)
            button.add(row)
            button.connect(
                "clicked",
                lambda _button, tab_id=tab.id: self._handle_clicked(tab_id),
            )
            self._pack(self.widget, button, expand=True)
            show_all = getattr(button, "show_all", None)
            if callable(show_all):
                show_all()
            self.buttons[tab.id] = button
            self.title_labels[tab.id] = title
            self.badges[tab.id] = badge
            self._apply_badge(badge, tab)
        self._tab_ids = tuple(tab.id for tab in tabs)
        self._apply_active()

    def _apply_badge(self, badge: Any, tab: TabVM) -> None:
        badge.set_text(str(tab.badge_count))
        badge.set_visible(tab.badge_count > 0)
        for level in _BADGE_LEVELS:
            if level != tab.badge_level:
                _remove_css_class(badge, level)
        if tab.badge_level:
            add_css_class(badge, tab.badge_level)

    def _apply_active(self) -> None:
        for tab_id, button in self.buttons.items():
            if tab_id == self.active:
                add_css_class(button, "active")
            else:
                _remove_css_class(button, "active")

    def _handle_clicked(self, tab_id: str) -> None:
        self.set_active(tab_id)
        self._on_select(tab_id)

    def update(self, tabs: tuple[TabVM, ...]) -> None:
        if tuple(tab.id for tab in tabs) != self._tab_ids:
            self._build_tabs(tabs)
            return
        for tab in tabs:
            self.title_labels[tab.id].set_text(tab.label)
            self._apply_badge(self.badges[tab.id], tab)

    def set_active(self, tab_id: str) -> None:
        self.active = tab_id
        self._apply_active()
