from __future__ import annotations

import re
import subprocess
from typing import Callable, Literal

ThemePreference = Literal["system", "light", "dark"]
ResolvedTheme = Literal["light", "dark"]

THEME_PREFERENCES: tuple[ThemePreference, ...] = ("system", "light", "dark")
_THEME_SET = frozenset(THEME_PREFERENCES)


def normalize_theme_preference(value: object, default: ThemePreference = "system") -> ThemePreference:
    if isinstance(value, str) and value in _THEME_SET:
        return value  # type: ignore[return-value]
    return default


def resolve_theme(
    preference: str,
    *,
    system_is_dark: Callable[[], bool] | None = None,
) -> ResolvedTheme:
    """Map Settings.theme → concrete light/dark class."""
    pref = normalize_theme_preference(preference)
    if pref == "dark":
        return "dark"
    if pref == "light":
        return "light"
    detector = system_is_dark if system_is_dark is not None else detect_system_dark
    return "dark" if detector() else "light"


def detect_system_dark() -> bool:
    """Best-effort desktop dark detection for Cinnamon / GNOME / GTK."""
    color_scheme = _gsettings_get("org.gnome.desktop.interface", "color-scheme")
    if color_scheme is not None and "prefer-dark" in color_scheme:
        return True
    if color_scheme is not None and "prefer-light" in color_scheme:
        return False

    for schema in (
        "org.cinnamon.desktop.interface",
        "org.gnome.desktop.interface",
    ):
        theme_name = _gsettings_get(schema, "gtk-theme")
        if theme_name and _theme_name_looks_dark(theme_name):
            return True

    prefer = _gtk_prefer_dark_theme()
    if prefer is not None:
        return prefer
    return False


def _theme_name_looks_dark(name: str) -> bool:
    return bool(re.search(r"dark", name, flags=re.IGNORECASE))


def _gsettings_get(schema: str, key: str) -> str | None:
    try:
        completed = subprocess.run(
            ["gsettings", "get", schema, key],
            check=False,
            capture_output=True,
            text=True,
            timeout=1.0,
        )
    except (OSError, subprocess.SubprocessError):
        return None
    if completed.returncode != 0:
        return None
    raw = completed.stdout.strip()
    if len(raw) >= 2 and raw[0] == raw[-1] and raw[0] in "'\"":
        raw = raw[1:-1]
    return raw


def _gtk_prefer_dark_theme() -> bool | None:
    import os

    if not os.environ.get("DISPLAY"):
        return None
    try:
        import gi

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

        settings = Gtk.Settings.get_default()
        if settings is None:
            return None
        return bool(settings.get_property("gtk-application-prefer-dark-theme"))
    except Exception:
        return None
