"""
Rich-based theming system for ScalarGrad.

Provides named style palettes and a shared console instance. Users can
switch themes at runtime via :func:`set_theme` or by setting
``scalargrad.config.theme`` before importing other modules.

Example::

    import scalargrad
    scalargrad.set_theme("cyberpunk")          # switch globally
    scalargrad.console.print("[header]Hello![/header]")

    from scalargrad import list_themes, THEMES
    print(list_themes())
"""

from __future__ import annotations

import io
import sys
from dataclasses import dataclass
from typing import Dict, Optional

from rich.console import Console
from rich.theme import Theme

# ---------------------------------------------------------------------------
# StyleTheme dataclass — every semantic colour / style token lives here
# ---------------------------------------------------------------------------

@dataclass
class StyleTheme:
    """
    Semantic style token mapping for ScalarGrad output.

    All values must be valid `rich` style strings (e.g. ``"bold bright_cyan"``).
    Users can create their own palette by instantiating :class:`StyleTheme` and
    passing it to :func:`register_theme`.
    """
    # Structural elements
    header:        str = "bold bright_cyan"
    subheader:     str = "bold cyan"
    banner_border: str = "bold bright_cyan"

    # Data tokens
    label:  str = "bold white"
    value:  str = "bold bright_green"
    key:    str = "bright_blue"
    code:   str = "bright_white on grey7"

    # Status tokens
    success: str = "bold bright_green"
    warning: str = "bold yellow"
    error:   str = "bold bright_red"
    info:    str = "dim cyan"

    # Tables
    table_header: str = "bold bright_cyan"
    table_border: str = "bright_blue"

    # Progress / spinners
    progress_bar:  str = "bright_green"
    progress_text: str = "cyan"


# ---------------------------------------------------------------------------
# Built-in palettes
# ---------------------------------------------------------------------------

THEMES: Dict[str, StyleTheme] = {
    # --- default: blue/green sci-fi (dark terminal) ---
    "default": StyleTheme(
        header="bold bright_cyan",
        subheader="bold cyan",
        banner_border="bold bright_cyan",
        label="bold white",
        value="bold bright_green",
        key="bright_blue",
        code="bright_white on grey7",
        success="bold bright_green",
        warning="bold yellow",
        error="bold bright_red",
        info="dim cyan",
        table_header="bold bright_cyan",
        table_border="bright_blue",
        progress_bar="bright_green",
        progress_text="cyan",
    ),

    # --- dark: deep purple / cyan electro ---
    "dark": StyleTheme(
        header="bold bright_magenta",
        subheader="bold magenta",
        banner_border="bold bright_magenta",
        label="bold white",
        value="bold bright_cyan",
        key="medium_purple3",
        code="bright_white on grey3",
        success="bold bright_cyan",
        warning="bold yellow",
        error="bold bright_red",
        info="dim magenta",
        table_header="bold bright_magenta",
        table_border="medium_purple3",
        progress_bar="bright_cyan",
        progress_text="magenta",
    ),

    # --- cyberpunk: neon magenta / yellow ---
    "cyberpunk": StyleTheme(
        header="bold bright_magenta on grey3",
        subheader="bold yellow",
        banner_border="bold bright_magenta",
        label="bold bright_yellow",
        value="bold bright_magenta",
        key="bold yellow",
        code="bright_magenta on black",
        success="bold bright_yellow",
        warning="bold orange3",
        error="bold red1",
        info="dim yellow",
        table_header="bold bright_yellow",
        table_border="bright_magenta",
        progress_bar="bright_magenta",
        progress_text="bright_yellow",
    ),

    # --- light: muted tones for bright-background terminals ---
    "light": StyleTheme(
        header="bold blue",
        subheader="bold dark_cyan",
        banner_border="bold blue",
        label="bold black",
        value="bold dark_green",
        key="navy_blue",
        code="black on grey82",
        success="bold dark_green",
        warning="bold dark_goldenrod",
        error="bold red3",
        info="dark_cyan",
        table_header="bold blue",
        table_border="steel_blue",
        progress_bar="green3",
        progress_text="dark_cyan",
    ),

    # --- minimal: bold/italic only, no colour ---
    "minimal": StyleTheme(
        header="bold",
        subheader="bold italic",
        banner_border="bold",
        label="bold",
        value="italic",
        key="bold",
        code="dim",
        success="bold",
        warning="italic",
        error="bold italic",
        info="dim",
        table_header="bold",
        table_border="dim",
        progress_bar="",
        progress_text="dim",
    ),
}


# ---------------------------------------------------------------------------
# Console factory & active console management
# ---------------------------------------------------------------------------

_active_theme_name: str = "default"
_active_console: Optional[Console] = None


def _build_console(theme_name: str) -> Console:
    """Build a :class:`~rich.console.Console` wired to the named theme."""
    if theme_name not in THEMES:
        raise ValueError(
            f"Unknown theme {theme_name!r}. "
            f"Available themes: {list(THEMES)}"
        )
    t = THEMES[theme_name]
    rich_theme = Theme(
        {
            "header":        t.header,
            "subheader":     t.subheader,
            "banner_border": t.banner_border,
            "label":         t.label,
            "value":         t.value,
            "key":           t.key,
            "code":          t.code,
            "success":       t.success,
            "warning":       t.warning,
            "error":         t.error,
            "info":          t.info,
            "table_header":  t.table_header,
            "table_border":  t.table_border,
            "progress_bar":  t.progress_bar,
            "progress_text": t.progress_text,
        }
    )
    # Provide a UTF-8–capable stream so that Unicode box-drawing characters
    # and other non-ASCII glyphs work regardless of the terminal's default
    # encoding (e.g., cp1252 on legacy Windows consoles).
    # We open fd 1 (stdout) with UTF-8 and closefd=False so the underlying
    # file descriptor is NOT closed when the wrapper is garbage-collected.
    # Reconfigure sys.stdout to UTF-8 so box-drawing / Unicode chars work on
    # Windows (cp1252 default) without wrapping in a separate file object.
    # Using the original stdout lets rich properly detect TTY / ANSI support,
    # which is required for Live / Progress cursor-overwrite to work correctly.
    try:
        sys.stdout.reconfigure(encoding="utf-8", errors="replace")  # type: ignore[attr-defined]
    except (AttributeError, io.UnsupportedOperation):
        pass  # already UTF-8, or non-reconfigurable (IDLE, Jupyter, etc.)

    return Console(
        theme=rich_theme,
        highlight=False,
        legacy_windows=False,
    )


def set_theme(name: str) -> None:
    """
    Switch the active theme globally.

    Args:
        name: One of the keys in :data:`THEMES`, or a name previously
              registered via :func:`register_theme`.

    Raises:
        ValueError: If *name* is not a known theme.

    Example::

        import scalargrad
        scalargrad.set_theme("cyberpunk")
    """
    global _active_theme_name, _active_console
    _active_theme_name = name
    _active_console = _build_console(name)


def register_theme(name: str, theme: StyleTheme) -> None:
    """
    Register a custom theme so it can be activated by name.

    Args:
        name:  Unique identifier string.
        theme: A :class:`StyleTheme` instance.

    Example::

        from scalargrad.theme import StyleTheme, register_theme
        my_theme = StyleTheme(header="bold red", value="bold green")
        register_theme("my_theme", my_theme)
        import scalargrad; scalargrad.set_theme("my_theme")
    """
    THEMES[name] = theme


def get_console() -> Console:
    """
    Return the active :class:`~rich.console.Console` instance.

    Lazily initialises with ``"default"`` theme if :func:`set_theme` has
    not been called yet.
    """
    global _active_console
    if _active_console is None:
        _active_console = _build_console(_active_theme_name)
    return _active_console


def list_themes() -> list:
    """Return the names of all registered themes."""
    return list(THEMES.keys())


def get_current_theme_name() -> str:
    """Return the name of the currently active theme."""
    return _active_theme_name


# Module-level convenience alias — other modules import this directly.
# It resolves lazily so theme changes made via set_theme() are picked up.
class _ConsoleLazyProxy:
    """Transparent proxy that always delegates to the active console."""

    def __getattr__(self, name: str):  # type: ignore[override]
        return getattr(get_console(), name)


console: Console = _ConsoleLazyProxy()  # type: ignore[assignment]
