#!/usr/bin/env python3
"""
Brightness control overlay — always-on-top icon in bottom-right; click to show controls.
Uses Wayland layer-shell OVERLAY so it stays above Chromium fullscreen/kiosk.
Requires: brightness-api.service running (listens 127.0.0.1:8765).
"""
import logging
import sys
import urllib.request
import urllib.error

LOG_TAG = "brightness-overlay"
logging.basicConfig(
    stream=sys.stderr,
    level=logging.INFO,
    format=f"{LOG_TAG}: %(message)s",
)
log = logging.getLogger(LOG_TAG)

try:
    import gi
    gi.require_version("Gdk", "3.0")
    gi.require_version("Gtk", "3.0")
    from gi.repository import Gdk, Gtk, GLib
except Exception as e:
    log.error("need PyGObject Gtk: %s", e)
    sys.exit(1)

HAS_LAYER_SHELL = False
try:
    gi.require_version("GtkLayerShell", "0.1")
    from gi.repository import GtkLayerShell
    HAS_LAYER_SHELL = True
except Exception:
    log.warning("GtkLayerShell not available, using regular window")

API_URL = "http://127.0.0.1:8765/brightness"
API_STATE_URL = "http://127.0.0.1:8765/state"

# Sizes
ICON_SIZE = 44
EXPANDED_WIDTH = 300
PANEL_HEIGHT = 48


def get_current_state():
    """Return (level 1–5, is_auto). On error return (None, True) so Auto appears active."""
    try:
        with urllib.request.urlopen(API_STATE_URL, timeout=1) as r:
            text = r.read().decode()
        level = None
        override = "0"
        for line in text.strip().splitlines():
            line = line.strip()
            if line.startswith("level="):
                level = line.split("=", 1)[1].strip()
            elif line.startswith("override="):
                override = line.split("=", 1)[1].strip()
        if level and level.isdigit() and 1 <= int(level) <= 5:
            return int(level), override == "0"
        return (None, True)
    except Exception:
        return (None, True)


def set_brightness(level):
    try:
        req = urllib.request.Request(
            API_URL,
            data=f"level={level}".encode(),
            method="POST",
            headers={"Content-Type": "application/x-www-form-urlencoded"},
        )
        with urllib.request.urlopen(req, timeout=2) as r:
            log.info("brightness %s: %s", level, r.read().decode().strip())
    except urllib.error.URLError as e:
        log.warning("API call failed (is brightness-api.service running?): %s", e)
    except Exception as e:
        log.warning("set_brightness failed: %s", e)


def update_active_buttons(level_buttons, auto_btn, current_level, is_auto):
    """Set .active style on the button that matches current state."""
    for i, btn in enumerate(level_buttons):
        ctx = btn.get_style_context()
        if not is_auto and current_level == i + 1:
            ctx.add_class("active")
        else:
            ctx.remove_class("active")
    if auto_btn:
        ctx = auto_btn.get_style_context()
        if is_auto:
            ctx.add_class("active")
        else:
            ctx.remove_class("active")


def make_window_layer_shell(win):
    GtkLayerShell.init_for_window(win)
    GtkLayerShell.set_layer(win, GtkLayerShell.Layer.OVERLAY)
    # Anchor only to bottom and right so the surface sits in the bottom-right corner
    GtkLayerShell.set_anchor(win, GtkLayerShell.Edge.TOP, False)
    GtkLayerShell.set_anchor(win, GtkLayerShell.Edge.LEFT, False)
    GtkLayerShell.set_anchor(win, GtkLayerShell.Edge.BOTTOM, True)
    GtkLayerShell.set_anchor(win, GtkLayerShell.Edge.RIGHT, True)
    GtkLayerShell.set_keyboard_mode(win, GtkLayerShell.KeyboardMode.NONE)
    GtkLayerShell.set_margin(win, GtkLayerShell.Edge.BOTTOM, 12)
    GtkLayerShell.set_margin(win, GtkLayerShell.Edge.RIGHT, 12)
    if hasattr(GtkLayerShell, "set_namespace"):
        GtkLayerShell.set_namespace(win, "brightness-overlay")
    if hasattr(GtkLayerShell, "set_exclusive_zone"):
        GtkLayerShell.set_exclusive_zone(win, 0)
    log.info("layer-shell OVERLAY anchored bottom-right (always on top)")


def position_fallback_bottom_right(win, width=None, height=None):
    """Position window in bottom-right (X11: only works with toplevel, not POPUP)."""
    if width is None:
        width = ICON_SIZE
    if height is None:
        height = ICON_SIZE
    display = Gdk.Display.get_default()
    if not display:
        return
    n = display.get_n_monitors()
    monitor = display.get_monitor(0) if n else None
    if not monitor:
        return
    geom = monitor.get_geometry()
    x = geom.x + geom.width - width - 12
    y = geom.y + geom.height - height - 12
    win.move(x, y)


def make_window_fallback(win):
    # Toplevel (not POPUP) so we can position and have no decorations on X11
    win.set_keep_above(True)
    win.set_decorated(False)
    win.set_type_hint(Gdk.WindowTypeHint.UTILITY)  # tool-style window, often undecorated
    # Position after map so WM doesn't override
    win.connect("map-event", lambda w, e: position_fallback_bottom_right(w))
    log.info("X11 fallback: keep-above, undecorated, bottom-right")


def main():
    # Normal toplevel: on X11 we can position it and set_decorated(False) removes title bar.
    # (POPUP without parent cannot be positioned on X11 and gets "temporary window" / centering.)
    win = Gtk.Window()
    win.set_decorated(False)
    win.set_resizable(False)
    win.set_default_size(ICON_SIZE, ICON_SIZE)
    win.set_skip_taskbar_hint(True)
    win.set_skip_pager_hint(True)
    # Transparent window: need RGBA visual and app-paintable
    screen = Gdk.Screen.get_default()
    if screen and screen.get_rgba_visual():
        win.set_visual(screen.get_rgba_visual())
    win.set_app_paintable(True)

    # CSS: transparent window and overlay; only subtle hover so icon stays visible
    style = """
    GtkWindow, window {
        background-color: transparent;
        background-image: none;
    }
    .brightness-icon-btn {
        background-color: transparent;
        border-radius: 8px;
        padding: 4px;
        min-width: 36px;
        min-height: 36px;
    }
    .brightness-icon-btn:hover {
        background-color: rgba(0, 0, 0, 0.25);
    }
    .brightness-panel {
        background-color: transparent;
        border-radius: 8px;
        padding: 6px;
    }
    .brightness-panel.expanded {
        background-color: rgba(0, 0, 0, 0.5);
    }
    .brightness-panel button {
        min-width: 36px;
        font-weight: bold;
    }
    .brightness-panel button.level-btn.active {
        background-color: rgba(255, 255, 255, 0.35);
        border: 1px solid rgba(255, 255, 255, 0.6);
    }
    """
    css = Gtk.CssProvider()
    css.load_from_data(style.encode())
    Gtk.StyleContext.add_provider_for_screen(
        Gdk.Screen.get_default(),
        css,
        Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
    )

    # Main horizontal box: [revealer with controls] [icon button]
    main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)

    # Left part: revealer with Brightness + 1–5 + Auto
    strip_box = Gtk.Box(spacing=6)
    strip_box.set_margin_start(8)
    strip_box.set_margin_top(6)
    strip_box.set_margin_bottom(6)
    strip_box.set_margin_end(4)
    strip_box.get_style_context().add_class("brightness-panel")

    strip_box.pack_start(Gtk.Label(label="Brightness "), False, False, 0)
    level_buttons = []
    for i in range(1, 6):
        btn = Gtk.Button(label=str(i))
        btn.get_style_context().add_class("level-btn")
        btn.set_can_focus(False)
        strip_box.pack_start(btn, False, False, 0)
        level_buttons.append(btn)
    auto_btn = Gtk.Button(label="Auto")
    auto_btn.get_style_context().add_class("level-btn")
    auto_btn.set_can_focus(False)
    strip_box.pack_start(auto_btn, False, False, 0)

    def refresh_active_state():
        level, is_auto = get_current_state()
        update_active_buttons(level_buttons, auto_btn, level or 3, is_auto)

    def on_level_click(btn, level):
        set_brightness(level)
        # Optimistic update so active state shows immediately
        if level == "auto":
            update_active_buttons(level_buttons, auto_btn, 3, True)
        else:
            update_active_buttons(level_buttons, auto_btn, int(level), False)
        # Sync from API after a short delay (daemon updates state file periodically)
        def delayed_refresh():
            refresh_active_state()
            return False
        GLib.timeout_add(500, delayed_refresh)

    for i, btn in enumerate(level_buttons):
        btn.connect("clicked", on_level_click, str(i + 1))
    auto_btn.connect("clicked", on_level_click, "auto")

    revealer = Gtk.Revealer()
    revealer.set_reveal_child(False)
    revealer.set_transition_type(Gtk.RevealerTransitionType.SLIDE_LEFT)
    revealer.set_transition_duration(150)
    revealer.add(strip_box)

    main_box.pack_start(revealer, False, False, 0)

    # Right part: always-visible icon button (click toggles panel)
    try:
        icon_img = Gtk.Image.new_from_icon_name("display-brightness-symbolic", Gtk.IconSize.BUTTON)
    except Exception:
        icon_img = Gtk.Label(label="☀")
    icon_btn = Gtk.Button()
    icon_btn.get_style_context().add_class("brightness-icon-btn")
    icon_btn.set_can_focus(False)
    icon_btn.add(icon_img)
    icon_btn.set_relief(Gtk.ReliefStyle.NONE)

    expanded = [False]  # use list so closure can mutate
    use_fallback = not (HAS_LAYER_SHELL and GtkLayerShell.is_supported())

    def on_icon_clicked(btn):
        expanded[0] = not expanded[0]
        revealer.set_reveal_child(expanded[0])
        ctx = strip_box.get_style_context()
        if expanded[0]:
            ctx.add_class("expanded")
            GLib.idle_add(refresh_active_state)
            win.resize(EXPANDED_WIDTH, PANEL_HEIGHT)
            if use_fallback:
                position_fallback_bottom_right(win, EXPANDED_WIDTH, PANEL_HEIGHT)
        else:
            ctx.remove_class("expanded")
            win.resize(ICON_SIZE, ICON_SIZE)
            if use_fallback:
                position_fallback_bottom_right(win, ICON_SIZE, ICON_SIZE)

    icon_btn.connect("clicked", on_icon_clicked)
    main_box.pack_end(icon_btn, False, False, 0)

    win.add(main_box)

    if HAS_LAYER_SHELL and GtkLayerShell.is_supported():
        make_window_layer_shell(win)
    else:
        make_window_fallback(win)

    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    GLib.idle_add(refresh_active_state)
    log.info("brightness icon visible (click to show controls)")
    Gtk.main()


if __name__ == "__main__":
    main()
