import tkinter as tk
from typing import Any, Callable, Dict, List, Optional, Tuple

try:
    import customtkinter as ctk
except ImportError:
    import unittest.mock as _mock

    ctk = _mock.MagicMock()

from antigravity_provider.router.ui.theme import Theme
from antigravity_provider.router.ui.assets import AssetManager


class HubButton(ctk.CTkButton):
    """Design-system compliant button supporting primary, secondary, ghost, danger and accent_outline variants."""

    def __init__(
        self,
        master: Any,
        text: str,
        variant: str = "primary",  # primary | secondary | ghost | danger | accent_outline
        command: Optional[Callable] = None,
        width: int = 120,
        height: int = Theme.HEIGHT_BTN_MD,
        font: Optional[Tuple[str, int, str]] = None,
        image: Optional[ctk.CTkImage] = None,
        **kwargs,
    ):
        if font is None:
            font = Theme.font_subheading() if variant in ("primary", "danger") else Theme.font_body()

        fg_color = Theme.ACCENT
        hover_color = Theme.ACCENT_HOVER
        text_color = Theme.TEXT_ON_ACCENT
        border_width = 0
        border_color = None

        if variant == "primary":
            fg_color = Theme.ACCENT
            hover_color = Theme.ACCENT_HOVER
            text_color = Theme.TEXT_ON_ACCENT
        elif variant == "secondary":
            fg_color = Theme.SURFACE
            hover_color = Theme.SURFACE_HOVER
            text_color = Theme.TEXT_PRIMARY
            border_width = 1
            border_color = Theme.BORDER
        elif variant == "ghost":
            fg_color = "transparent"
            hover_color = Theme.SURFACE_HOVER
            text_color = Theme.TEXT_PRIMARY
        elif variant == "danger":
            fg_color = Theme.DANGER_SURFACE
            hover_color = Theme.DANGER_SURFACE_HOVER
            text_color = Theme.DANGER_TEXT
            border_width = 1
            border_color = Theme.DANGER_BORDER
        elif variant == "accent_outline":
            fg_color = "transparent"
            hover_color = Theme.ACCENT_DIM
            text_color = Theme.ACCENT
            border_width = 1
            border_color = Theme.ACCENT

        btn_kwargs = {
            "master": master,
            "text": text,
            "command": command,
            "width": width,
            "height": height,
            "corner_radius": Theme.RADIUS_SM,
            "fg_color": fg_color,
            "hover_color": hover_color,
            "text_color": text_color,
            "border_width": border_width,
            "font": font,
            "image": image,
        }
        if border_color is not None:
            btn_kwargs["border_color"] = border_color
        btn_kwargs.update(kwargs)

        super().__init__(**btn_kwargs)


class HubCard(ctk.CTkFrame):
    """Card container with standardized surface color, subtle border and corner radius."""

    def __init__(
        self,
        master: Any,
        corner_radius: int = Theme.RADIUS_MD,
        border_width: int = 1,
        border_color: Optional[str] = None,
        fg_color: Optional[str] = None,
        **kwargs,
    ):
        super().__init__(
            master=master,
            corner_radius=corner_radius,
            border_width=border_width,
            border_color=border_color or Theme.BORDER,
            fg_color=fg_color or Theme.SURFACE,
            **kwargs,
        )


def enable_clipboard_shortcuts(entry_widget: Any) -> None:
    """Enable robust clipboard (Ctrl+V, Ctrl+C, Ctrl+X, Ctrl+A, Shift+Insert) across English and Cyrillic layouts."""
    inner = getattr(entry_widget, "_entry", entry_widget)

    def _paste_handler(event=None):
        try:
            text = entry_widget.clipboard_get()
            if text is not None:
                try:
                    inner.delete("sel.first", "sel.last")
                except Exception:
                    pass
                inner.insert("insert", text)
            return "break"
        except Exception:
            return "break"

    def _select_all_handler(event=None):
        try:
            inner.select_range(0, "end")
            inner.icursor("end")
            return "break"
        except Exception:
            return "break"

    def _copy_handler(event=None):
        try:
            if inner.select_present():
                sel = inner.selection_get()
                entry_widget.clipboard_clear()
                entry_widget.clipboard_append(sel)
            return "break"
        except Exception:
            pass

    def _cut_handler(event=None):
        try:
            if inner.select_present():
                sel = inner.selection_get()
                entry_widget.clipboard_clear()
                entry_widget.clipboard_append(sel)
                inner.delete("sel.first", "sel.last")
            return "break"
        except Exception:
            pass

    # Standard Latin shortcuts
    for p in (
        "<Control-v>",
        "<Control-V>",
        "<Control-KeyPress-v>",
        "<Control-KeyPress-V>",
        "<Shift-Insert>",
        "<Shift-KeyPress-Insert>",
    ):
        try:
            inner.bind(p, _paste_handler, add=False)
        except Exception:
            pass
    for a in ("<Control-a>", "<Control-A>", "<Control-KeyPress-a>", "<Control-KeyPress-A>"):
        try:
            inner.bind(a, _select_all_handler, add=False)
        except Exception:
            pass
    for c in ("<Control-c>", "<Control-C>", "<Control-KeyPress-c>", "<Control-KeyPress-C>"):
        try:
            inner.bind(c, _copy_handler, add=False)
        except Exception:
            pass
    for x in ("<Control-x>", "<Control-X>", "<Control-KeyPress-x>", "<Control-KeyPress-X>"):
        try:
            inner.bind(x, _cut_handler, add=False)
        except Exception:
            pass

    # Windows Cyrillic / Russian keyboard layouts
    for p in ("<Control-KeyPress-1084>", "<Control-KeyPress-1052>", "<Control-cyrillic_em>", "<Control-Cyrillic_EM>"):
        try:
            inner.bind(p, _paste_handler, add=False)
        except Exception:
            pass
    for a in ("<Control-KeyPress-1092>", "<Control-KeyPress-1060>", "<Control-cyrillic_ef>", "<Control-Cyrillic_EF>"):
        try:
            inner.bind(a, _select_all_handler, add=False)
        except Exception:
            pass
    for c in ("<Control-KeyPress-1089>", "<Control-KeyPress-1057>", "<Control-cyrillic_es>", "<Control-Cyrillic_ES>"):
        try:
            inner.bind(c, _copy_handler, add=False)
        except Exception:
            pass
    for x in ("<Control-KeyPress-1095>", "<Control-KeyPress-1063>", "<Control-cyrillic_che>", "<Control-Cyrillic_CHE>"):
        try:
            inner.bind(x, _cut_handler, add=False)
        except Exception:
            pass


class HubEntry(ctk.CTkEntry):
    """Design-system compliant Entry with automatic clipboard & keyboard layout support."""

    def __init__(self, master: Any, **kwargs):
        super().__init__(master=master, **kwargs)
        enable_clipboard_shortcuts(self)


class HubSectionHeader(ctk.CTkFrame):
    """Section title with optional subtitle and right-aligned action button."""

    def __init__(
        self,
        master: Any,
        title: str,
        subtitle: Optional[str] = None,
        action_text: Optional[str] = None,
        action_cmd: Optional[Callable] = None,
        **kwargs,
    ):
        super().__init__(master=master, fg_color="transparent", **kwargs)

        left = ctk.CTkFrame(self, fg_color="transparent")
        left.pack(side="left", fill="both", expand=True)

        ctk.CTkLabel(
            left,
            text=title,
            font=Theme.font_title_page(),
            text_color=Theme.TEXT_PRIMARY,
        ).pack(anchor="w")

        if subtitle:
            ctk.CTkLabel(
                left,
                text=subtitle,
                font=Theme.font_caption(),
                text_color=Theme.TEXT_MUTED,
            ).pack(anchor="w", pady=(Theme.SPACE_XS, 0))

        if action_text and action_cmd:
            HubButton(
                self,
                text=action_text,
                variant="primary",
                command=action_cmd,
                height=Theme.HEIGHT_BTN_MD,
            ).pack(side="right", padx=(Theme.SPACE_MD, 0))


class HubStatusBadge(ctk.CTkFrame):
    """Status pill with dot and localized label for all normalized health states."""

    STATUS_LABELS = {
        "healthy": "Работает",
        "quota_low": "Квота заканчивается",
        "quota_exhausted": "Квота исчерпана",
        "cooldown": "Ожидание сброса",
        "rate_limited": "Лимит запросов",
        "not_configured": "Аккаунт не добавлен",
        "auth_required": "Требуется вход",
        "auth_expired": "Требуется повторная авторизация",
        "disabled": "Отключён",
        "cold_spare": "Холодный резерв",
        "unhealthy": "Ошибка",
        "not_tested": "Не проверен",
    }

    def __init__(self, master: Any, status_key: str, **kwargs):
        super().__init__(master=master, fg_color=Theme.SURFACE_MUTED, corner_radius=Theme.RADIUS_PILL, **kwargs)

        inner = ctk.CTkFrame(self, fg_color="transparent")
        inner.pack(padx=Theme.SPACE_MD, pady=Theme.SPACE_XS)

        self.dot = ctk.CTkLabel(
            inner,
            text="●",
            font=Theme.font_badge_bold(),
        )
        self.dot.pack(side="left", padx=(0, Theme.SPACE_XS))

        self.label = ctk.CTkLabel(
            inner,
            font=Theme.font_caption(),
            text_color=Theme.TEXT_SECONDARY,
        )
        self.label.pack(side="left")
        self.set_status(status_key)

    def set_status(self, status_key: str, label: Optional[str] = None) -> None:
        clean_key = (status_key or "unknown").lower().replace("-", "_")
        default_label = self.STATUS_LABELS.get(clean_key, status_key or "Н/Д")
        color = _semantic_color(clean_key)
        self.dot.configure(text_color=color)
        self.label.configure(text=label or default_label)


class HubProviderBadge(ctk.CTkFrame):
    """Badge indicating the AI provider with real provider mark."""

    def __init__(self, master: Any, provider: str, size: Tuple[int, int] = (18, 18), **kwargs):
        super().__init__(master=master, fg_color="transparent", **kwargs)

        prov = provider.lower()
        if "antigravity" in prov:
            color = Theme.PROVIDER_ANTIGRAVITY
            name = "Google Antigravity"
        elif "codex" in prov or "openai" in prov:
            color = Theme.PROVIDER_CODEX
            name = "OpenAI Codex"
        elif "opencode" in prov:
            color = Theme.PROVIDER_OPENCODE
            name = "OpenCode Go"
        else:
            color = Theme.TEXT_MUTED
            name = provider

        badge = ctk.CTkFrame(self, fg_color=Theme.SURFACE_MUTED, corner_radius=Theme.RADIUS_SM)
        badge.pack(side="left")

        img = AssetManager.get().get_provider_image(prov, size=size)
        if img:
            ctk.CTkLabel(badge, image=img, text="").pack(
                side="left", padx=(Theme.SPACE_SM, Theme.SPACE_XS), pady=Theme.SPACE_XS
            )

        ctk.CTkLabel(
            badge,
            text=name,
            font=Theme.font_caption(),
            text_color=color,
        ).pack(side="left", padx=(0 if img else Theme.SPACE_SM, Theme.SPACE_SM), pady=Theme.SPACE_XS)


class HubMetricCard(HubCard):
    """Dashboard metric card with icon, large number and label."""

    def __init__(
        self,
        master: Any,
        title: str,
        value: str,
        subtext: str,
        icon: str = "◈",
        accent: bool = False,
        **kwargs,
    ):
        border_color = Theme.BORDER_ACCENT if accent else Theme.BORDER
        super().__init__(master=master, border_color=border_color, **kwargs)

        top = ctk.CTkFrame(self, fg_color="transparent")
        top.pack(fill="x", padx=Theme.CARD_PAD_X, pady=(Theme.CARD_PAD_Y, Theme.SPACE_XS))

        ctk.CTkLabel(
            top,
            text=icon,
            font=Theme.font_icon(),
            text_color=Theme.ACCENT if accent else Theme.TEXT_MUTED,
        ).pack(side="left", padx=(0, Theme.SPACE_SM))

        ctk.CTkLabel(
            top,
            text=title.upper(),
            font=Theme.font_micro(),
            text_color=Theme.TEXT_MUTED,
        ).pack(side="left")

        val_color = Theme.ACCENT if accent else Theme.TEXT_PRIMARY
        self.val_label = ctk.CTkLabel(
            self,
            text=value,
            font=Theme.font_metric(),
            text_color=val_color,
        )
        self.val_label.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_XS, Theme.SPACE_XS))

        self.sub_label = ctk.CTkLabel(
            self,
            text=subtext,
            font=Theme.font_caption(),
            text_color=Theme.TEXT_SECONDARY,
        )
        self.sub_label.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(0, Theme.CARD_PAD_Y))


class HubToolbar(ctk.CTkFrame):
    """Cockpit-grade toolbar with Search, Category Filter, and Sorting."""

    def __init__(
        self,
        master: Any,
        on_search: Optional[Callable[[str], None]] = None,
        on_filter: Optional[Callable[[str], None]] = None,
        on_sort: Optional[Callable[[str], None]] = None,
        **kwargs,
    ):
        super().__init__(master=master, fg_color="transparent", **kwargs)
        self.on_search = on_search
        self.on_filter = on_filter
        self.on_sort = on_sort

        # Search Entry
        self.search_entry = ctk.CTkEntry(
            self,
            placeholder_text="🔍 Поиск аккаунта или модели...",
            font=Theme.font_body(),
            height=Theme.HEIGHT_BTN_MD,
            width=260,
            fg_color=Theme.SURFACE,
            border_color=Theme.BORDER,
            text_color=Theme.TEXT_PRIMARY,
        )
        self.search_entry.pack(side="left", padx=(0, Theme.INLINE_GAP))
        self.search_entry.bind("<KeyRelease>", self._handle_search)

        # Filter Option Menu
        self.filter_menu = ctk.CTkOptionMenu(
            self,
            values=["Все статусы", "Подключённые", "Требуют входа", "Квота исчерпана"],
            font=Theme.font_caption(),
            height=Theme.HEIGHT_BTN_MD,
            width=160,
            fg_color=Theme.SURFACE,
            button_color=Theme.SECONDARY,
            text_color=Theme.TEXT_PRIMARY,
            command=self._handle_filter,
        )
        self.filter_menu.pack(side="left", padx=(0, Theme.INLINE_GAP))

        # Sort Option Menu
        self.sort_menu = ctk.CTkOptionMenu(
            self,
            values=["По умолчанию", "По имени", "По статусу", "По провайдеру"],
            font=Theme.font_caption(),
            height=Theme.HEIGHT_BTN_MD,
            width=150,
            fg_color=Theme.SURFACE,
            button_color=Theme.SECONDARY,
            text_color=Theme.TEXT_PRIMARY,
            command=self._handle_sort,
        )
        self.sort_menu.pack(side="left")

    def _handle_search(self, event=None):
        if self.on_search:
            self.on_search(self.search_entry.get().strip().lower())

    def _handle_filter(self, choice: str):
        if self.on_filter:
            self.on_filter(choice)

    def _handle_sort(self, choice: str):
        if self.on_sort:
            self.on_sort(choice)


class HubModal(ctk.CTkToplevel):
    """Reusable modal dialog on dark backdrop."""

    def __init__(self, parent: Any, title: str, width: int = 580, height: int = 500):
        super().__init__(parent)
        self.title(title)
        self.geometry(f"{width}x{height}")
        self.configure(fg_color=Theme.BG_WINDOW)
        self.resizable(False, False)
        self.transient(parent)
        self.grab_set()

        self.update_idletasks()
        px = parent.winfo_x() + (parent.winfo_width() - width) // 2
        py = parent.winfo_y() + (parent.winfo_height() - height) // 2
        self.geometry(f"+{px}+{py}")

        self.container = HubCard(
            self,
            corner_radius=Theme.RADIUS_LG,
            border_color=Theme.BORDER_ACCENT,
            # Contrast-safe in dark, hybrid and light palettes.
            fg_color=Theme.SURFACE,
        )
        self.container.pack(fill="both", expand=True, padx=Theme.SPACE_LG, pady=Theme.SPACE_LG)

        self.hdr = ctk.CTkFrame(self.container, fg_color="transparent")
        self.hdr.pack(fill="x", padx=Theme.CARD_PAD_X, pady=(Theme.CARD_PAD_Y, Theme.SPACE_SM))

        self.title_lbl = ctk.CTkLabel(
            self.hdr,
            text=title,
            font=Theme.font_title_section(),
            text_color=Theme.TEXT_PRIMARY,
        )
        self.title_lbl.pack(side="left")

        self.body = ctk.CTkFrame(self.container, fg_color="transparent")
        self.body.pack(fill="both", expand=True, padx=Theme.CARD_PAD_X, pady=Theme.SPACE_SM)

        self.footer = ctk.CTkFrame(self.container, fg_color="transparent")
        self.footer.pack(fill="x", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_SM, Theme.CARD_PAD_Y))


# Public design-system names. Existing Hub-prefixed imports remain compatible.
SectionHeader = HubSectionHeader
StatusBadge = HubStatusBadge
ProviderBadge = HubProviderBadge


def _semantic_color(status: str) -> str:
    """Return an operational colour without using brand gold as health."""
    key = (status or "unknown").lower().replace("-", "_")
    if key in {"healthy", "active", "authenticated", "online", "ready"}:
        return Theme.COLOR_POSITIVE
    if key in {"warning", "quota_low", "cooldown", "reserve", "rate_limited", "auth_required", "auth_expired"}:
        return Theme.COLOR_CAUTION
    if key in {"error", "unhealthy", "quota_exhausted", "offline"}:
        return Theme.COLOR_NEGATIVE
    return Theme.COLOR_NEUTRAL


def ellipsize_text(text: str, max_chars: int = 34) -> str:
    """Truncate long identities while retaining the full value for a tooltip."""
    value = str(text or "")
    if len(value) <= max_chars:
        return value
    return f"{value[: max(1, max_chars - 1)]}…"


class Tooltip:
    """Lightweight tooltip implemented with Tk only; no extra dependency."""

    def __init__(self, widget: Any, text: str):
        self.widget = widget
        self.text = text
        self._window: Optional[tk.Toplevel] = None
        widget.bind("<Enter>", self._show, add="+")
        widget.bind("<Leave>", self._hide, add="+")

    def _show(self, _event=None) -> None:
        if not self.text or self._window is not None:
            return
        self._window = tk.Toplevel(self.widget)
        self._window.wm_overrideredirect(True)
        self._window.wm_geometry(f"+{self.widget.winfo_rootx() + Theme.SPACE_MD}+{self.widget.winfo_rooty() + 28}")
        label = tk.Label(
            self._window,
            text=self.text,
            background=Theme.BG_HEADER,
            foreground=Theme.TEXT_PRIMARY,
            relief="solid",
            borderwidth=1,
            font=Theme.font_caption(),
        )
        label.pack(padx=Theme.SPACE_SM, pady=Theme.SPACE_XS)

    def _hide(self, _event=None) -> None:
        if self._window is not None:
            self._window.destroy()
            self._window = None


class EllipsizedLabel(ctk.CTkLabel):
    """Label that shortens long account identities and exposes the full value."""

    def __init__(self, master: Any, text: str, max_chars: int = 34, **kwargs):
        self.full_text = str(text or "")
        self.max_chars = max_chars
        super().__init__(master, text=ellipsize_text(self.full_text, max_chars), **kwargs)
        self.tooltip = Tooltip(self, self.full_text) if len(self.full_text) > max_chars else None

    def set_text(self, text: str) -> None:
        self.full_text = str(text or "")
        self.configure(text=ellipsize_text(self.full_text, self.max_chars))
        if self.tooltip:
            self.tooltip.text = self.full_text
        elif len(self.full_text) > self.max_chars:
            self.tooltip = Tooltip(self, self.full_text)


class _TextBadge(ctk.CTkFrame):
    """Small neutral badge used for plans and metadata, not health."""

    def __init__(self, master: Any, text: str, text_color: Optional[str] = None, **kwargs):
        super().__init__(
            master=master,
            height=Theme.HEIGHT_BADGE,
            fg_color=Theme.SURFACE_MUTED,
            border_width=1,
            border_color=Theme.BORDER_SUBTLE,
            corner_radius=Theme.RADIUS_PILL,
            **kwargs,
        )
        self.label = ctk.CTkLabel(
            self,
            text=text,
            font=Theme.font_micro(),
            text_color=text_color or Theme.TEXT_SECONDARY,
        )
        self.label.pack(padx=Theme.SPACE_SM, pady=Theme.SPACE_XS)

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


class PlanBadge(_TextBadge):
    """Provider plan badge that makes provider truth distinct from inference."""

    TRUSTED_SOURCES = {"provider_api", "jwt_claim", "provider_auth"}

    def set_plan(self, plan_code: str, plan_source: str) -> bool:
        """Update the badge and return whether it should be visible."""
        code = str(plan_code or "UNKNOWN").strip().upper()
        source = str(plan_source or "unknown").strip().lower()
        if code == "UNKNOWN" or source == "unknown":
            return False
        if source in self.TRUSTED_SOURCES:
            self.set_text(f"Тариф {code}")
            self.configure(border_color=Theme.BORDER_ACCENT, fg_color=Theme.SURFACE_MUTED)
            self.label.configure(text_color=Theme.TEXT_ACCENT)
        else:
            self.set_text(f"Тариф {code} • выведено")
            self.configure(border_color=Theme.BORDER_SUBTLE, fg_color=Theme.SURFACE_MUTED)
            self.label.configure(text_color=Theme.TEXT_MUTED)
        return True


class QuotaBar(ctk.CTkFrame):
    """Compact quota bar with honest unknown state and semantic thresholds."""

    def __init__(
        self,
        master: Any,
        value: Optional[float] = None,
        label: str = "Квота",
        detail: str = "Н/Д",
        **kwargs,
    ):
        super().__init__(master=master, fg_color="transparent", **kwargs)
        self.header = ctk.CTkFrame(self, fg_color="transparent")
        self.header.pack(fill="x")
        self.label = ctk.CTkLabel(self.header, text=label, font=Theme.font_caption(), text_color=Theme.TEXT_SECONDARY)
        self.label.pack(side="left")
        self.detail = ctk.CTkLabel(self.header, text=detail, font=Theme.font_micro(), text_color=Theme.TEXT_MUTED)
        self.detail.pack(side="right")
        self.progress = ctk.CTkProgressBar(
            self,
            height=Theme.SPACE_SM,
            corner_radius=Theme.RADIUS_PILL,
            fg_color=Theme.BG_SIDEBAR,
            progress_color=Theme.COLOR_NEUTRAL,
        )
        self.progress.pack(fill="x", pady=(Theme.SPACE_XS, 0))
        self.set_value(value, detail)

    def set_value(self, value: Optional[float], detail: Optional[str] = None) -> None:
        if value is None:
            self.progress.set(0)
            self.progress.configure(progress_color=Theme.COLOR_NEUTRAL)
            self.detail.configure(text=detail or "Н/Д", text_color=Theme.TEXT_MUTED)
            return
        normalized = max(0.0, min(1.0, float(value)))
        color = (
            Theme.COLOR_NEGATIVE
            if normalized <= 0.05
            else (Theme.COLOR_CAUTION if normalized <= 0.20 else Theme.COLOR_POSITIVE)
        )
        self.progress.set(normalized)
        self.progress.configure(progress_color=color)
        self.detail.configure(text=detail or f"{normalized:.0%}", text_color=color)


class QuotaBucketWidget(HubCard):
    """Reusable quota bucket updated in place by a stable bucket key."""

    def __init__(
        self,
        master: Any,
        bucket_key: str,
        label: str,
        remaining_ratio: Optional[float] = None,
        reset_text: str = "",
        is_estimated: bool = False,
        **kwargs,
    ):
        super().__init__(master, corner_radius=Theme.RADIUS_SM, border_color=Theme.BORDER_SUBTLE, **kwargs)
        self.bucket_key = bucket_key
        self.title = ctk.CTkLabel(self, text="", font=Theme.font_body_bold(), text_color=Theme.TEXT_PRIMARY)
        self.title.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_SM, Theme.SPACE_XS))
        self.bar = QuotaBar(self, label="Остаток")
        self.bar.pack(fill="x", padx=Theme.CARD_PAD_X)
        self.reset = ctk.CTkLabel(self, text="", font=Theme.font_micro(), text_color=Theme.TEXT_MUTED)
        self.reset.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_XS, 0))
        self.unavailable_reason = ctk.CTkLabel(
            self,
            text="",
            font=Theme.font_micro(),
            text_color=Theme.COLOR_CAUTION,
            wraplength=320,
            justify="left",
        )
        self.update_bucket(label, remaining_ratio, reset_text, is_estimated)

    def update_bucket(
        self,
        label: str,
        remaining_ratio: Optional[float],
        reset_text: str = "",
        is_estimated: bool = False,
        detail: Optional[str] = None,
        unavailable_reason: Optional[str] = None,
    ) -> None:
        suffix = " • оценка" if is_estimated else ""
        self.title.configure(text=f"{label}{suffix}")
        self.bar.set_value(remaining_ratio, detail or ("Н/Д" if remaining_ratio is None else None))
        self.reset.configure(text=reset_text or "Сброс: Н/Д")
        if unavailable_reason:
            self.reset.pack_configure(pady=(Theme.SPACE_XS, 0))
            self.unavailable_reason.configure(text=f"Недоступно: {unavailable_reason}")
            self.unavailable_reason.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_XS, Theme.SPACE_SM))
        else:
            self.unavailable_reason.pack_forget()
            self.reset.pack_configure(pady=(Theme.SPACE_XS, Theme.SPACE_SM))


class SearchField(HubEntry):
    """Normalized searchable text field with clipboard support."""

    def __init__(self, master: Any, placeholder_text: str = "Поиск…", width: int = 260, **kwargs):
        super().__init__(
            master,
            placeholder_text=placeholder_text,
            width=width,
            height=Theme.HEIGHT_INPUT,
            font=Theme.font_body(),
            fg_color=Theme.SURFACE,
            border_color=Theme.BORDER,
            text_color=Theme.TEXT_PRIMARY,
            **kwargs,
        )


class FilterButton(ctk.CTkOptionMenu):
    """Compact filter selector with a consistent visual treatment."""

    def __init__(self, master: Any, values: List[str], command: Optional[Callable] = None, **kwargs):
        super().__init__(
            master,
            values=values,
            command=command,
            height=Theme.HEIGHT_INPUT,
            font=Theme.font_caption(),
            fg_color=Theme.SURFACE,
            button_color=Theme.SECONDARY,
            button_hover_color=Theme.SURFACE_HOVER,
            text_color=Theme.TEXT_PRIMARY,
            **kwargs,
        )


class ActionButton(HubButton):
    """Semantic alias used for text actions in views."""


class IconButton(HubButton):
    """Square icon action using the existing lightweight asset infrastructure."""

    def __init__(self, master: Any, text: str, size: int = Theme.HEIGHT_BTN_SM, **kwargs):
        super().__init__(master, text=text, width=size, height=size, variant="ghost", **kwargs)


class EmptyState(HubCard):
    """Honest empty/unknown state with an optional recovery action."""

    def __init__(
        self,
        master: Any,
        title: str,
        message: str,
        action_text: Optional[str] = None,
        action_cmd: Optional[Callable] = None,
        **kwargs,
    ):
        super().__init__(master, border_color=Theme.BORDER_SUBTLE, fg_color=Theme.SURFACE_MUTED, **kwargs)
        ctk.CTkLabel(self, text=title, font=Theme.font_heading(), text_color=Theme.TEXT_PRIMARY).pack(
            pady=(Theme.SPACE_LG, Theme.SPACE_XS)
        )
        ctk.CTkLabel(self, text=message, font=Theme.font_caption(), text_color=Theme.TEXT_MUTED).pack(
            padx=Theme.SPACE_LG, pady=(0, Theme.SPACE_MD)
        )
        if action_text and action_cmd:
            ActionButton(self, text=action_text, variant="secondary", command=action_cmd).pack(pady=(0, Theme.SPACE_LG))


class RouteTargetWidget(HubCard):
    """One node in a primary → reserve failover chain."""

    def __init__(self, master: Any, rank: str, title: str, subtitle: str, status: str = "unknown", **kwargs):
        super().__init__(master, corner_radius=Theme.RADIUS_SM, **kwargs)
        self.rank = ctk.CTkLabel(self, text=rank, font=Theme.font_micro(), text_color=Theme.TEXT_MUTED)
        self.rank.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_SM, 0))
        self.title = ctk.CTkLabel(self, text=title, font=Theme.font_body_bold(), text_color=Theme.TEXT_PRIMARY)
        self.title.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_XS, 0))
        self.subtitle = ctk.CTkLabel(self, text=subtitle, font=Theme.font_micro(), text_color=Theme.TEXT_MUTED)
        self.subtitle.pack(anchor="w", padx=Theme.CARD_PAD_X)
        self.quota = ctk.CTkLabel(self, text="", font=Theme.font_micro(), text_color=Theme.TEXT_MUTED)
        self.quota.pack(anchor="w", padx=Theme.CARD_PAD_X)
        self.quota_tooltip = Tooltip(self.quota, "")
        self.failover = ctk.CTkLabel(
            self, text="", font=Theme.font_micro(), text_color=Theme.COLOR_CAUTION, wraplength=260, justify="left"
        )
        self.update_target(rank, title, subtitle, status)

    def update_target(
        self,
        rank: str,
        title: str,
        subtitle: str,
        status: str = "unknown",
        quota_status: str = "unknown",
        failover_reason: Optional[str] = None,
    ) -> None:
        self.rank.configure(text=rank)
        self.title.configure(text=title)
        self.subtitle.configure(text=subtitle)
        quota_labels = {
            "healthy": "Квота: доступна",
            "warning": "Квота: заканчивается",
            "exhausted": "Квота: исчерпана",
        }
        if quota_status in quota_labels:
            quota_text = quota_labels[quota_status]
            quota_reason = ""
        elif status in {"not_configured", "auth_required", "auth_expired"}:
            quota_text = "Квота: Н/Д — аккаунт не подключён"
            quota_reason = "Аккаунт не авторизован, поэтому лимиты запросить нельзя"
        else:
            quota_text = "Квота: Н/Д — провайдер не отдаёт лимиты"
            quota_reason = "В текущем ответе провайдера отсутствуют измеримые данные квоты"
        self.quota.configure(text=quota_text)
        self.quota_tooltip.text = quota_reason
        if failover_reason:
            self.failover.configure(text=f"Переключено: {failover_reason}")
            self.failover.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_XS, Theme.SPACE_SM))
        else:
            self.failover.pack_forget()
            self.quota.pack_configure(pady=(0, Theme.SPACE_SM))
        self.set_status(status)

    def set_status(self, status: str) -> None:
        self.configure(border_color=_semantic_color(status))


class AccountCardWidget(HubCard):
    """Keyed account card whose quota buckets are updated without rebuilding the card."""

    MANAGEMENT_ACTIONS = (
        ("test", "⚡ Тест"),
        ("set_main", "★ Основной"),
        ("set_orchestrator", "♛ Оркестратор"),
        ("assign_role", "Назначить роль"),
    )

    def __init__(
        self,
        master: Any,
        profile_id: str,
        identity: str,
        provider: str,
        status: str = "unknown",
        compact: bool = False,
        on_action: Optional[Callable[[str, Any], None]] = None,
        **kwargs,
    ):
        super().__init__(master, height=148, **kwargs)
        self.pack_propagate(False)
        self.grid_propagate(False)
        self.profile_id = profile_id
        self.profile_model: Any = None
        self.on_action = on_action
        self.compact = True
        self._quota_widgets: Dict[str, QuotaBucketWidget] = {}
        self.widgets_created = 0
        self.widgets_destroyed = 0

        top = ctk.CTkFrame(self, fg_color="transparent")
        top.pack(fill="x", padx=Theme.CARD_PAD_X, pady=(Theme.CARD_PAD_Y, Theme.SPACE_XS))
        self.provider = ctk.CTkLabel(top, text=provider, font=Theme.font_micro(), text_color=Theme.TEXT_MUTED)
        self.provider.pack(side="left")
        self.profile_mark = ctk.CTkButton(
            top,
            text="⋮",
            width=28,
            height=20,
            fg_color="transparent",
            hover_color=Theme.SURFACE_HOVER,
            text_color=Theme.TEXT_SECONDARY,
            command=lambda: self._trigger("account_details"),
        )
        self.profile_mark.pack(side="right")

        self.identity = EllipsizedLabel(self, text=identity, font=Theme.font_body_bold(), text_color=Theme.TEXT_PRIMARY)
        self.identity.pack(anchor="w", padx=Theme.CARD_PAD_X)
        self.meta = ctk.CTkLabel(self, text="", font=Theme.font_micro(), text_color=Theme.TEXT_MUTED)
        self.meta.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_XS, 0))
        self.badges = ctk.CTkFrame(self, fg_color="transparent")
        self.badges.pack(fill="x", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_XS, Theme.SPACE_SM))
        self.plan_badge = PlanBadge(self.badges, text="")
        self.status = StatusBadge(self.badges, status)
        self.status.pack(side="left")

        self.compact_quota = ctk.CTkFrame(self, fg_color="transparent")
        self.compact_quota_cells: list[dict[str, Any]] = []
        for index in range(4):
            cell = ctk.CTkFrame(self.compact_quota, fg_color="transparent")
            cell.grid(row=index // 2, column=index % 2, sticky="nsew", padx=(0, 8), pady=(1, 2))
            top_line = ctk.CTkFrame(cell, fg_color="transparent")
            top_line.pack(fill="x")
            title = ctk.CTkLabel(top_line, text="", font=Theme.font_micro(), text_color=Theme.TEXT_SECONDARY, anchor="w")
            title.pack(side="left")
            value = ctk.CTkLabel(top_line, text="", font=Theme.font_micro_bold(), text_color=Theme.TEXT_PRIMARY, anchor="e")
            value.pack(side="right")
            progress = ctk.CTkProgressBar(
                cell,
                height=4,
                corner_radius=2,
                fg_color=Theme.SURFACE_MUTED,
                progress_color=Theme.STATUS_HEALTHY,
            )
            progress.pack(fill="x", pady=(1, 0))
            progress.set(0)
            reset = ctk.CTkLabel(cell, text="", font=Theme.font_micro(), text_color=Theme.TEXT_MUTED, anchor="w")
            reset.pack(fill="x")
            self.compact_quota_cells.append(
                {"frame": cell, "title": title, "value": value, "progress": progress, "reset": reset}
            )
            self.compact_quota.grid_columnconfigure(index % 2, weight=1)
        self.compact_actions = ctk.CTkFrame(self, fg_color="transparent")
        for text, action in (
            ("⚡", "test"),
            ("★", "set_main"),
            ("♛", "set_orchestrator"),
            ("Роль", "assign_role"),
            ("↻", "refresh_account"),
        ):
            ActionButton(
                self.compact_actions,
                text=text,
                variant="ghost",
                width=48,
                height=Theme.HEIGHT_BTN_SM,
                command=lambda action_name=action: self._trigger(action_name),
            ).pack(side="left", padx=(0, Theme.SPACE_XS))

        self.details = ctk.CTkFrame(self, fg_color="transparent")
        self.details.pack(fill="x")
        self.quota_box = ctk.CTkFrame(self.details, fg_color="transparent")
        self.quota_box.pack(fill="x", padx=Theme.CARD_PAD_X)
        self.empty_quota = ctk.CTkLabel(
            self.quota_box, text="Квота: Н/Д", font=Theme.font_caption(), text_color=Theme.TEXT_MUTED
        )
        self.actions = ctk.CTkFrame(self.details, fg_color="transparent")
        self.actions.pack(fill="x", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_SM, Theme.CARD_PAD_Y))
        self.action_feedback = ctk.CTkLabel(
            self.actions,
            text="",
            font=Theme.font_caption(),
            text_color=Theme.TEXT_SECONDARY,
            wraplength=330,
            justify="left",
            anchor="w",
        )
        self.action_buttons: Dict[str, ActionButton] = {}
        management = ctk.CTkFrame(self.actions, fg_color="transparent")
        management.pack(fill="x", pady=(0, Theme.SPACE_XS))
        for index, (action, label) in enumerate(self.MANAGEMENT_ACTIONS):
            button = ActionButton(
                management,
                text=label,
                variant="secondary" if index < 2 else "ghost",
                height=Theme.HEIGHT_BTN_SM,
                command=lambda action_name=action: self._trigger(action_name),
            )
            button.grid(row=index // 2, column=index % 2, padx=Theme.SPACE_XS, pady=Theme.SPACE_XS, sticky="ew")
            management.grid_columnconfigure(index % 2, weight=1)
            self.action_buttons[action] = button
        utility = ctk.CTkFrame(self.actions, fg_color="transparent")
        utility.pack(fill="x")
        ActionButton(
            utility,
            text="Обновить",
            variant="secondary",
            width=86,
            command=lambda: self._trigger("refresh_account"),
        ).pack(side="left")
        ActionButton(
            utility,
            text="Удалить",
            variant="ghost",
            width=72,
            command=lambda: self._trigger("delete_credentials"),
        ).pack(side="right")
        self.set_compact(True)
        self._bind_details_click(self)
        try:
            self._canvas.configure(takefocus=1)
        except (AttributeError, tk.TclError):
            pass
        self.bind("<Return>", self._open_details, add="+")
        self.bind("<space>", self._open_details, add="+")
        self.bind("<Enter>", self._hover_on, add="+")
        self.bind("<Leave>", self._hover_off, add="+")

    def _bind_details_click(self, widget: Any) -> None:
        if isinstance(widget, ctk.CTkButton):
            return
        widget.configure(cursor="hand2")
        widget.bind("<Button-1>", self._open_details, add="+")
        for child in widget.winfo_children():
            self._bind_details_click(child)

    def _open_details(self, _event: Any = None) -> str:
        self._trigger("account_details")
        return "break"

    def _hover_on(self, _event: Any = None) -> None:
        self.configure(fg_color=Theme.SURFACE_HOVER, border_color=Theme.BORDER_HOVER)

    def _hover_off(self, _event: Any = None) -> None:
        profile = self.profile_model
        border = Theme.BORDER_ACCENT if profile and getattr(profile, "is_main_account", False) else Theme.BORDER
        self.configure(fg_color=Theme.SURFACE, border_color=border)

    @staticmethod
    def resolve_identity(profile: Any) -> str:
        """Contract order: email → normalized account identity → display name → profile id."""
        for value in (
            getattr(profile, "email", None),
            getattr(profile, "account_identity", None),
            getattr(profile, "display_name", None),
            getattr(profile, "profile_id", None),
        ):
            if value and str(value).strip() and str(value).strip() not in {"—", "Н/Д"}:
                return str(value).strip()
        return "Аккаунт: Н/Д"

    def _trigger(self, action: str) -> None:
        if self.on_action and self.profile_model is not None:
            self.set_action_feedback("Выполняется…", None)
            self.on_action(action, self.profile_model)

    def set_action_feedback(self, message: str, success: Optional[bool]) -> None:
        """Show an action result where the user initiated it."""
        color = Theme.TEXT_SECONDARY
        if success is True:
            color = Theme.STATUS_HEALTHY
        elif success is False:
            color = Theme.STATUS_ERROR
        self.action_feedback.configure(text=message, text_color=color)
        self.action_feedback.pack(fill="x", pady=(0, Theme.SPACE_XS), before=self.actions.winfo_children()[1])

    def toggle_compact(self) -> None:
        # Account cards intentionally have one fixed Cockpit-style layout.
        self.set_compact(True)

    def set_compact(self, compact: bool) -> None:
        self.compact = True
        self.details.pack_forget()
        self.compact_quota.pack(fill="x", padx=Theme.CARD_PAD_X, pady=(0, Theme.SPACE_XS))
        self.compact_actions.pack_forget()

    def update_account(self, profile: Any, quota_snapshot: Optional[Any] = None) -> None:
        self.profile_model = profile
        self.profile_id = profile.profile_id
        self.identity.set_text(self.resolve_identity(profile))
        self.provider.configure(text=profile.provider_display_name or profile.provider)
        roles = ", ".join(getattr(profile, "assigned_roles", []) or []) or "Роль: Н/Д"
        self.meta.configure(text=f"{profile.display_name} • {roles}")
        if self.plan_badge.set_plan(
            getattr(profile, "plan_code", "UNKNOWN"), getattr(profile, "plan_source", "unknown")
        ):
            self.plan_badge.pack(side="left", padx=(0, Theme.SPACE_XS), before=self.status)
        else:
            self.plan_badge.pack_forget()
        self.configure(border_color=Theme.BORDER_ACCENT if profile.is_main_account else Theme.BORDER)

        snapshot = quota_snapshot or getattr(profile, "quota_snapshot", None)
        buckets = list(getattr(snapshot, "buckets", None) or [])
        estimated = bool(getattr(snapshot, "is_estimated", True)) if snapshot else True
        unavailable_reason = getattr(snapshot, "unavailable_reason", None) if snapshot else None
        measured_remaining = [
            float(bucket.remaining_percent)
            for bucket in buckets
            if getattr(bucket, "remaining_percent", None) is not None
        ]
        if measured_remaining and not estimated:
            if all(remaining <= 0 for remaining in measured_remaining):
                card_health, card_label = "quota_exhausted", "Квота исчерпана"
            elif any(remaining <= 0 for remaining in measured_remaining):
                card_health, card_label = "warning", "Часть квот исчерпана"
            else:
                card_health, card_label = "healthy", "Работает"
        else:
            card_health = profile.health_state
            card_label = getattr(profile, "health_label_ru", None)
        self.status.set_status(card_health, card_label)
        visible_buckets = sorted(
            buckets,
            key=lambda bucket: (
                getattr(bucket, "remaining_percent", None) is None,
                float(getattr(bucket, "remaining_percent", 101.0) or 0.0),
            ),
        )[:4]
        if visible_buckets:
            for index, cell in enumerate(self.compact_quota_cells):
                if index < len(visible_buckets):
                    bucket = visible_buckets[index]
                    remaining = getattr(bucket, "remaining_percent", None)
                    color = (
                        Theme.STATUS_HEALTHY
                        if getattr(bucket, "status", "unknown") == "healthy"
                        else Theme.STATUS_WARNING
                        if getattr(bucket, "status", "unknown") == "warning"
                        else (Theme.STATUS_ERROR if getattr(bucket, "status", "unknown") == "exhausted" else Theme.TEXT_MUTED)
                    )
                    detail = bucket.formatted_remaining()
                    if detail == "Н/Д" and unavailable_reason:
                        limit = getattr(bucket, "limit_absolute", None)
                        unit = getattr(bucket, "unit", None)
                        prefix = f"Лимит ${limit} • " if limit is not None and unit == "USD" else ""
                        detail = f"{prefix}{unavailable_reason}"
                    cell["title"].configure(text=bucket.display_name)
                    cell["value"].configure(text=detail, text_color=color)
                    cell["progress"].configure(progress_color=color)
                    cell["progress"].set(float(remaining) / 100.0 if remaining is not None else 0)
                    reset_txt = bucket.formatted_reset() or "Период указан провайдером"
                    cell["reset"].configure(text=reset_txt)
                    cell["frame"].grid(row=index // 2, column=index % 2, sticky="nsew", padx=(0, 8), pady=(1, 2))
                else:
                    cell["frame"].grid_remove()
        else:
            # Fallback when no bucket objects exist
            reason = unavailable_reason or (
                "Аккаунт не подключён" if profile.health_state in {"not_configured", "auth_required", "auth_expired"}
                else "Провайдер не отдаёт лимиты"
            )
            cell = self.compact_quota_cells[0]
            cell["title"].configure(text="Квота")
            cell["value"].configure(text=f"Н/Д", text_color=Theme.TEXT_MUTED)
            cell["progress"].configure(progress_color=Theme.COLOR_NEUTRAL)
            cell["progress"].set(0)
            cell["reset"].configure(text=reason)
            cell["frame"].grid(row=0, column=0, columnspan=2, sticky="nsew", padx=(0, 8), pady=(1, 2))
            for other in self.compact_quota_cells[1:]:
                other["frame"].grid_remove()
        seen: set[str] = set()
        for bucket in buckets:
            key = str(getattr(bucket, "id", "") or getattr(bucket, "display_name", "bucket"))
            seen.add(key)
            remaining = getattr(bucket, "remaining_percent", None)
            ratio = float(remaining) / 100.0 if remaining is not None else None
            if ratio is None:
                remaining_abs = getattr(bucket, "remaining_absolute", None)
                limit_abs = getattr(bucket, "limit_absolute", None)
                if remaining_abs is not None and limit_abs:
                    ratio = float(remaining_abs) / float(limit_abs)
            detail = bucket.formatted_remaining() if hasattr(bucket, "formatted_remaining") else "Н/Д"
            reset = bucket.formatted_reset() if hasattr(bucket, "formatted_reset") else None
            if key not in self._quota_widgets:
                widget = QuotaBucketWidget(self.quota_box, key, bucket.display_name)
                widget.pack(fill="x", pady=(0, Theme.SPACE_SM))
                self._quota_widgets[key] = widget
                self.widgets_created += 1
            self._quota_widgets[key].update_bucket(
                bucket.display_name,
                ratio,
                reset or "Сброс: Н/Д",
                estimated,
                detail,
                getattr(bucket, "unavailable_reason", None) or getattr(snapshot, "unavailable_reason", None),
            )

        for key in list(self._quota_widgets):
            if key not in seen:
                self._quota_widgets.pop(key).destroy()
                self.widgets_destroyed += 1
        if buckets:
            self.empty_quota.pack_forget()
        else:
            self.empty_quota.pack(anchor="w", pady=Theme.SPACE_SM)


class AgentCardWidget(HubCard):
    """Model-agnostic agent-card shell shared by team views."""

    def __init__(self, master: Any, name: str, role: str, detail: str = "Н/Д", **kwargs):
        super().__init__(master, **kwargs)
        ctk.CTkLabel(self, text=role, font=Theme.font_micro(), text_color=Theme.TEXT_MUTED).pack(
            anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.CARD_PAD_Y, Theme.SPACE_XS)
        )
        ctk.CTkLabel(self, text=name, font=Theme.font_body_bold(), text_color=Theme.TEXT_PRIMARY).pack(
            anchor="w", padx=Theme.CARD_PAD_X
        )
        ctk.CTkLabel(self, text=detail, font=Theme.font_caption(), text_color=Theme.TEXT_SECONDARY).pack(
            anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.SPACE_XS, Theme.CARD_PAD_Y)
        )


class ConfirmDialog(HubModal):
    """Reusable destructive/non-destructive confirmation dialog."""

    def __init__(
        self,
        parent: Any,
        title: str,
        message: str,
        on_confirm: Callable[[], None],
        destructive: bool = False,
    ):
        super().__init__(parent, title=title, width=440, height=240)
        ctk.CTkLabel(
            self.body,
            text=message,
            wraplength=380,
            justify="left",
            font=Theme.font_body(),
            text_color=Theme.TEXT_SECONDARY,
        ).pack(fill="x")
        ActionButton(self.footer, text="Отмена", variant="ghost", command=self.destroy).pack(side="right")

        def _confirm() -> None:
            self.destroy()
            on_confirm()

        ActionButton(
            self.footer,
            text="Подтвердить",
            variant="danger" if destructive else "primary",
            command=_confirm,
        ).pack(side="right", padx=(0, Theme.SPACE_SM))


class Toast(HubCard):
    """Non-blocking in-window notification with an optional auto-dismiss timer."""

    def __init__(self, master: Any, message: str, status: str = "unknown", duration_ms: int = 4000, **kwargs):
        super().__init__(master, border_color=_semantic_color(status), fg_color=Theme.BG_HEADER, **kwargs)
        ctk.CTkLabel(self, text=message, font=Theme.font_caption(), text_color=Theme.TEXT_PRIMARY).pack(
            padx=Theme.CARD_PAD_X, pady=Theme.CARD_PAD_Y
        )
        if duration_ms > 0:
            self.after(duration_ms, self.destroy)
