"""Tema claro/oscuro siguiendo el esquema de color del sistema.

Usa el estilo Fusion (consistente entre plataformas) y aplica una paleta clara u
oscura según ``QStyleHints.colorScheme()``. Los widgets que no fuerzan colores
propios (consola de log, fondos) siguen automáticamente esta paleta.
"""

from __future__ import annotations

import subprocess
from collections.abc import Callable

from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPalette
from PySide6.QtWidgets import QApplication

# Modos de tema seleccionables por el usuario.
THEME_SYSTEM = "system"
THEME_LIGHT = "light"
THEME_DARK = "dark"
THEME_MODES = (THEME_SYSTEM, THEME_LIGHT, THEME_DARK)


def _portal_is_dark() -> bool | None:
    """Consulta el portal XDG (org.freedesktop.appearance color-scheme).

    Devuelve True (1=dark), False (2=light) o None (0=sin preferencia / no disponible).
    Es la fuente más fiable en Linux cuando Qt no detecta el esquema.
    """
    try:
        out = subprocess.run(
            [
                "gdbus", "call", "--session",
                "--dest", "org.freedesktop.portal.Desktop",
                "--object-path", "/org/freedesktop/portal/desktop",
                "--method", "org.freedesktop.portal.Settings.Read",
                "org.freedesktop.appearance", "color-scheme",
            ],
            capture_output=True, text=True, timeout=2,
        )
    except (OSError, subprocess.SubprocessError):
        return None
    if out.returncode != 0:
        return None
    if "uint32 1" in out.stdout:
        return True
    if "uint32 2" in out.stdout:
        return False
    return None


def _gsettings_is_dark() -> bool | None:
    """Respaldo: gsettings color-scheme de GNOME ('prefer-dark')."""
    try:
        out = subprocess.run(
            ["gsettings", "get", "org.gnome.desktop.interface", "color-scheme"],
            capture_output=True, text=True, timeout=2,
        )
    except (OSError, subprocess.SubprocessError):
        return None
    if out.returncode != 0:
        return None
    return "dark" in out.stdout.lower()


def system_is_dark(app: QApplication) -> bool:
    """True si el sistema está en modo oscuro.

    Prioriza el aviso de Qt; si Qt dice claro (o no soporta la API), consulta el
    portal XDG y gsettings, que en sesiones xcb suelen ser más fiables que Qt.
    """
    hints = app.styleHints()
    if hasattr(hints, "colorScheme"):
        try:
            if hints.colorScheme() == Qt.ColorScheme.Dark:
                return True
        except Exception:  # noqa: BLE001 - APIs viejas/raras: caer al fallback
            pass
    for probe in (_portal_is_dark, _gsettings_is_dark):
        val = probe()
        if val is not None:
            return val
    return app.palette().color(QPalette.Window).lightness() < 128


def _dark_palette() -> QPalette:
    p = QPalette()
    window = QColor(0x2b, 0x2b, 0x2b)
    base = QColor(0x1e, 0x1e, 0x1e)
    alt = QColor(0x35, 0x35, 0x35)
    text = QColor(0xdc, 0xdc, 0xdc)
    disabled = QColor(0x7f, 0x7f, 0x7f)
    highlight = QColor(0x2a, 0x82, 0xda)

    p.setColor(QPalette.Window, window)
    p.setColor(QPalette.WindowText, text)
    p.setColor(QPalette.Base, base)
    p.setColor(QPalette.AlternateBase, alt)
    p.setColor(QPalette.ToolTipBase, window)
    p.setColor(QPalette.ToolTipText, text)
    p.setColor(QPalette.Text, text)
    p.setColor(QPalette.Button, alt)
    p.setColor(QPalette.ButtonText, text)
    p.setColor(QPalette.BrightText, Qt.red)
    p.setColor(QPalette.Link, highlight)
    p.setColor(QPalette.Highlight, highlight)
    p.setColor(QPalette.HighlightedText, Qt.black)
    p.setColor(QPalette.PlaceholderText, disabled)
    for role in (QPalette.WindowText, QPalette.Text, QPalette.ButtonText):
        p.setColor(QPalette.Disabled, role, disabled)
    return p


def resolve_is_dark(app: QApplication, mode: str) -> bool:
    """Decide si pintar oscuro según el modo elegido (system/light/dark)."""
    if mode == THEME_DARK:
        return True
    if mode == THEME_LIGHT:
        return False
    return system_is_dark(app)  # THEME_SYSTEM (o valor desconocido)


def _repolish_all(app: QApplication) -> None:
    """Re-poliza todos los widgets para que adopten la nueva paleta en caliente.

    ``setPalette`` por sí solo no repinta los widgets ya creados: los que tienen
    stylesheet (pills) no re-resuelven ``palette(...)`` y algunos fondos base (la
    consola de log) no se redibujan. unpolish→polish→update fuerza ese refresco.
    """
    for widget in app.allWidgets():
        style = widget.style()
        style.unpolish(widget)
        style.polish(widget)
        widget.update()


def apply_theme(app: QApplication, mode: str = THEME_SYSTEM) -> None:
    """Aplica estilo Fusion + paleta clara/oscura según el modo elegido."""
    app.setStyle("Fusion")
    if resolve_is_dark(app, mode):
        app.setPalette(_dark_palette())
    else:
        app.setPalette(app.style().standardPalette())
    _repolish_all(app)


def watch_system_theme(app: QApplication, should_follow: Callable[[], bool]) -> None:
    """Re-aplica el tema al cambiar el esquema del sistema, solo si seguimos al sistema.

    ``should_follow`` se consulta en cada cambio: devuelve True cuando el modo activo
    es 'system' (si el usuario ha forzado claro/oscuro, ignoramos el cambio del SO).
    """
    hints = app.styleHints()
    if hasattr(hints, "colorSchemeChanged"):
        hints.colorSchemeChanged.connect(
            lambda _scheme: apply_theme(app, THEME_SYSTEM) if should_follow() else None
        )
