#!/usr/bin/env python3
"""AnduinOS Appearance — tweak taskbar style and position."""

import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
gi.require_version('Gio', '2.0')
from gi.repository import Gtk, Gdk, GLib, Adw, Gio
import subprocess
import json
import math
import shutil
import sys
import warnings
import gettext
import os
warnings.filterwarnings('ignore', category=DeprecationWarning, module='gi')
# suppress Adwaita deprecation warnings for APIs whose replacements
# (add_page, toast_overlay) do not yet exist in Ubuntu 24.04's Adwaita version
warnings.filterwarnings('ignore', message=r'.*Adw\.PreferencesWindow\.add.*', category=DeprecationWarning)
warnings.filterwarnings('ignore', message=r'.*Adw\.PreferencesWindow\.add_toast.*', category=DeprecationWarning)

# ── i18n ─────────────────────────────────────────────────────────
_LOCALE_DIR = os.environ.get('ANDUINOS_APPEARANCE_LOCALE',
                              os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                           '..', 'locale')
                              if os.path.isdir(os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                                            '..', 'locale'))
                              else '/usr/share/locale')
try:
    _ = gettext.translation('anduinos-appearance', _LOCALE_DIR, fallback=True).gettext
except Exception:
    _ = lambda s: s

ARC  = '/org/gnome/shell/extensions/arcmenu'
DTP  = '/org/gnome/shell/extensions/dash-to-panel'
SHELL_BASE = '/org/gnome/shell'
ARC_UUID = 'arcmenu@arcmenu.com'
DTP_UUID = 'dash-to-panel@jderose9.github.com'

# ── data ─────────────────────────────────────────────────────────

# (menu_layout, force_menu_location) per (style, position)
MENU_CONFIG = {
    # Classic — menu pinned to start-button corner
    ('classic', 'bottom'): ('arcmenu', 'BottomLeft'),
    ('classic', 'top'):    ('arcmenu', 'TopLeft'),
    ('classic', 'left'):   ('arcmenu', 'TopLeft'),
    ('classic', 'right'):  ('arcmenu', 'TopRight'),
    # Eleven — centered on horizontal panels; vertical panels keep default
    ('eleven',  'bottom'): ('11', 'BottomCentered'),
    ('eleven',  'top'):    ('11', 'TopCentered'),
    ('eleven',  'left'):   ('11', 'Off'),
    ('eleven',  'right'):  ('11', 'Off'),
}

POSITIONS = {
    'bottom': 'BOTTOM',
    'top':    'TOP',
    'left':   'LEFT',
    'right':  'RIGHT',
}

# ── panel-element-positions generators ───────────────────────────

def _make_pep(panel_pos: str, centered: bool) -> str:
    """Build the JSON for dash-to-panel panel-element-positions."""
    if centered:
        elements = [
            {"element": "activitiesButton", "visible": False, "position": "stackedTL"},
            {"element": "showAppsButton",   "visible": False, "position": "stackedTL"},
            {"element": "leftBox",          "visible": True,  "position": "stackedTL"},
            {"element": "centerBox",        "visible": True,  "position": "stackedBR"},
            {"element": "taskbar",          "visible": True,  "position": "centerMonitor"},
            {"element": "rightBox",         "visible": True,  "position": "stackedBR"},
            {"element": "systemMenu",       "visible": True,  "position": "stackedBR"},
            {"element": "dateMenu",         "visible": True,  "position": "stackedBR"},
            {"element": "desktopButton",    "visible": True,  "position": "stackedBR"},
        ]
    else:
        elements = [
            {"element": "centerBox",        "visible": True,  "position": "stackedTL"},
            {"element": "taskbar",          "visible": True,  "position": "stackedTL"},
            {"element": "showAppsButton",   "visible": False, "position": "stackedTL"},
            {"element": "activitiesButton", "visible": False, "position": "stackedBR"},
            {"element": "leftBox",          "visible": True,  "position": "stackedBR"},
            {"element": "rightBox",         "visible": True,  "position": "stackedBR"},
            {"element": "systemMenu",       "visible": True,  "position": "stackedBR"},
            {"element": "dateMenu",         "visible": True,  "position": "stackedBR"},
            {"element": "desktopButton",    "visible": True,  "position": "stackedBR"},
        ]
    return json.dumps({"0": elements})


def _dconf_read(key: str) -> str | None:
    try:
        r = subprocess.run(['dconf', 'read', key], capture_output=True, text=True)
        return r.stdout.strip() if r.returncode == 0 else None
    except Exception:
        return None


def _ext_is_enabled(uuid: str) -> bool:
    val = _dconf_read(f'{SHELL_BASE}/enabled-extensions')
    return uuid in val if val else False


def detect_current():
    """Detect current (style, position) from dconf. Returns ('eleven', 'bottom') as fallback."""
    ml = _dconf_read(f'{ARC}/menu-layout')
    style = 'classic' if (ml and 'arcmenu' in ml) else 'eleven'

    pp = _dconf_read(f'{DTP}/panel-positions')
    pos = 'bottom'
    if pp:
        for p in POSITIONS:
            if POSITIONS[p] in pp:
                pos = p
                break
    return style, pos


def _read_group_apps() -> bool:
    val = _dconf_read(f'{DTP}/group-apps')
    return val != 'false'  # default to True if unset or true


def _write_group_apps(enabled: bool):
    v = 'true' if enabled else 'false'
    subprocess.run(['dconf', 'write', f'{DTP}/group-apps', v], check=True)


def _read_use_launchers() -> bool:
    val = _dconf_read(f'{DTP}/group-apps-use-launchers')
    return val == 'true'


def _write_use_launchers(enabled: bool):
    v = 'true' if enabled else 'false'
    subprocess.run(['dconf', 'write', f'{DTP}/group-apps-use-launchers', v], check=True)


def apply_style_and_position(style: str, position: str) -> bool:
    """Apply the combination of *style* and *position* via dconf."""
    centered = (style == 'eleven')
    menu_layout, force_menu = MENU_CONFIG[(style, position)]

    panel_pos = POSITIONS[position]
    pep = _make_pep(panel_pos, centered)

    try:
        subprocess.run(['dconf', 'write', f'{DTP}/dot-position',              f"'{panel_pos}'"], check=True)
        subprocess.run(['dconf', 'write', f'{DTP}/panel-positions',           "'" + '{"0":"' + panel_pos + '"}' + "'"], check=True)
        subprocess.run(['dconf', 'write', f'{DTP}/panel-element-positions',   f"'{pep}'"], check=True)
        subprocess.run(['dconf', 'write', f'{ARC}/force-menu-location',       f"'{force_menu}'"], check=True)
        subprocess.run(['dconf', 'write', f'{ARC}/menu-layout',               f"'{menu_layout}'"], check=True)
        return True
    except subprocess.CalledProcessError:
        return False


# ── Cairo previews ───────────────────────────────────────────────

def rounded_rect(cr, x, y, w, h, r):
    cr.new_sub_path()
    cr.arc(x + w - r, y + r,     r, -math.pi / 2, 0)
    cr.arc(x + w - r, y + h - r, r, 0,            math.pi / 2)
    cr.arc(x + r,     y + h - r, r, math.pi / 2,  math.pi)
    cr.arc(x + r,     y + r,     r, math.pi,      3 * math.pi / 2)
    cr.close_path()


ICON_COLORS = [
    (0.95, 0.45, 0.20), (0.25, 0.55, 0.95), (0.25, 0.75, 0.45),
    (0.90, 0.25, 0.25), (0.85, 0.65, 0.15),
]


def _draw_icons(cr, x, y, icon_w, icon_h, icon_r, icon_gap, n):
    for i in range(n):
        ix = x + i * (icon_w + icon_gap)
        rounded_rect(cr, ix, y, icon_w, icon_h, icon_r)
        r, g, b = ICON_COLORS[i % len(ICON_COLORS)]
        cr.set_source_rgb(r, g, b)
        cr.fill()


def _draw_start_button(cr, x, y, w, h):
    rounded_rect(cr, x, y, w, h, 3)
    cr.set_source_rgb(0.25, 0.55, 0.95)
    cr.fill()
    cr.set_source_rgb(1, 1, 1)
    cx, cy = x + w / 2, y + h / 2
    s = 3
    cr.set_line_width(1.5)
    cr.move_to(cx - s, cy); cr.line_to(cx + s, cy)
    cr.move_to(cx, cy - s); cr.line_to(cx, cy + s)
    cr.stroke()


def _draw_sys_tray(cr, x, y):
    for j in range(4):
        cr.set_source_rgba(1, 1, 1, 0.45)
        cr.arc(x + j * 10, y + 4, 2.5, 0, 2 * math.pi)
        cr.fill()
    chev = x + 40
    cr.set_source_rgba(1, 1, 1, 0.35)
    cr.set_line_width(1.5)
    cr.move_to(chev, y); cr.line_to(chev + 5, y + 5); cr.line_to(chev, y + 8)
    cr.stroke()


def draw_preview(area, cr, w, h, style: str, position: str):
    """Full desktop preview with taskbar on *position* edge."""
    centered = (style == 'eleven')
    bar_thick = 18
    icon_w, icon_h = 14, 14
    icon_r = 3
    icon_gap = 5
    n_icons = 5
    start_w, start_h = 18, 12

    # desktop bg
    cr.set_source_rgb(0.12, 0.12, 0.14)
    cr.rectangle(0, 0, w, h)
    cr.fill()

    # ── bar coords ──
    if position == 'bottom':
        bx, by, bw, bh = 0, h - bar_thick, w, bar_thick
    elif position == 'top':
        bx, by, bw, bh = 0, 0, w, bar_thick
    elif position == 'left':
        bx, by, bw, bh = 0, 0, bar_thick, h
    else:  # right
        bx, by, bw, bh = w - bar_thick, 0, bar_thick, h

    is_horiz = position in ('bottom', 'top')

    # bar background
    cr.set_source_rgba(0.18, 0.18, 0.20, 0.85)
    cr.rectangle(bx, by, bw, bh)
    cr.fill()
    # highlight edge
    cr.set_source_rgba(1, 1, 1, 0.07)
    cr.set_line_width(1)
    if position == 'bottom':
        cr.move_to(bx, by); cr.line_to(bx + bw, by)
    elif position == 'top':
        cr.move_to(bx, by + bh); cr.line_to(bx + bw, by + bh)
    elif position == 'left':
        cr.move_to(bx + bw, by); cr.line_to(bx + bw, by + bh)
    else:
        cr.move_to(bx, by); cr.line_to(bx, by + bh)
    cr.stroke()

    if is_horiz:
        # ── horizontal bar: top/bottom ──
        pad = 6
        start_x = bx + pad
        start_y = by + 3
        _draw_start_button(cr, start_x, start_y, start_w, start_h)

        total_w = n_icons * icon_w + (n_icons - 1) * icon_gap
        if centered:
            icons_x = bx + (bw - total_w) / 2
        else:
            icons_x = start_x + start_w + 12
        icons_y = by + 2

        _draw_icons(cr, icons_x, icons_y, icon_w, icon_h, icon_r, icon_gap, n_icons)

        tray_x = bx + bw - 52
        tray_y = by + 5
        _draw_sys_tray(cr, tray_x, tray_y)

        cr.set_source_rgba(1, 1, 1, 0.5)
        cr.select_font_face('sans-serif')
        cr.set_font_size(6.5)
        cr.move_to(bx + bw - 90, by + 14)
        cr.show_text('12:34')
    else:
        # ── vertical bar: left/right ──
        start_x = bx + 2
        start_y = by + 6
        start_w_v, start_h_v = bar_thick - 4, bar_thick - 4
        _draw_start_button(cr, start_x, start_y, start_w_v, start_h_v)

        icon_w_v, icon_h_v = bar_thick - 6, bar_thick - 6
        total_h = n_icons * icon_h_v + (n_icons - 1) * icon_gap
        icons_y_v = by + 30 if not centered else by + (bh - total_h) / 2
        icons_x_v = bx + 3

        for i in range(n_icons):
            iy = icons_y_v + i * (icon_h_v + icon_gap)
            rounded_rect(cr, icons_x_v, iy, icon_w_v, icon_h_v, icon_r)
            r, g, b = ICON_COLORS[i % len(ICON_COLORS)]
            cr.set_source_rgb(r, g, b)
            cr.fill()

        # tiny clock at bottom of vertical bar
        cr.set_source_rgba(1, 1, 1, 0.45)
        cr.select_font_face('sans-serif')
        cr.set_font_size(5.5)
        cr.move_to(bx + 2, by + bh - 6)
        cr.show_text('12:34')


POS_LABELS = {'bottom': _('Bottom'), 'top': _('Top'), 'left': _('Left'), 'right': _('Right')}


# ── style page ───────────────────────────────────────────────────

class StylePage(Adw.PreferencesPage):
    def __init__(self, win):
        super().__init__()
        self.win = win
        self.set_title(_('Taskbar Style'))
        self.set_icon_name('preferences-desktop-theme-symbolic')

        # ── Preview group ──
        preview_group = Adw.PreferencesGroup()
        preview_group.set_title(_('Preview'))
        preview_group.set_description(_('Choose the visual style of your taskbar.'))

        card = Gtk.Frame()
        card.add_css_class('card')
        self.preview = Gtk.DrawingArea()
        self.preview.set_size_request(340, 110)
        self.preview.set_draw_func(self._draw, win.current_style, win.current_position)
        card.set_child(self.preview)
        preview_group.add(card)
        self.add(preview_group)

        # ── Layout group ──
        layout_group = Adw.PreferencesGroup()
        layout_group.set_title(_('Layout'))

        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
        self.btn11 = Gtk.Button(label='  11  ')
        self.btn11.connect('clicked', self._on_click, 'eleven')
        self.btn10 = Gtk.Button(label='  Classic  ')
        self.btn10.connect('clicked', self._on_click, 'classic')
        box.append(self.btn10)
        box.append(self.btn11)
        layout_group.add(box)

        # ── Position buttons ──
        self.pos_btns = {}
        pos_box = Gtk.FlowBox()
        pos_box.set_homogeneous(False)
        pos_box.set_selection_mode(Gtk.SelectionMode.NONE)
        pos_box.set_row_spacing(6)
        pos_box.set_column_spacing(6)
        pos_box.set_margin_top(12)
        for pos_label in ['bottom', 'top', 'left', 'right']:
            btn = Gtk.ToggleButton(label=_(POS_LABELS[pos_label]))
            btn.connect('toggled', self._on_pos_toggled, pos_label)
            pos_box.append(btn)
            self.pos_btns[pos_label] = btn
        layout_group.add(pos_box)
        self.add(layout_group)

        self._highlight_pos()

        # ── Group apps (Classic only) ──
        self.group_expander = Adw.ExpanderRow()
        self.group_expander.set_title(_('Group apps'))
        self.group_expander.set_subtitle(_('Combine multiple windows of one app into a single icon'))
        self.group_row = Adw.SwitchRow()
        self.group_row.set_title(_('Enable grouping'))
        self.group_row.set_active(_read_group_apps())
        self.group_row.connect('notify::active', self._on_group_toggled)
        self.group_expander.add_row(self.group_row)

        # ── Vista taskbar behavior (Classic only) ──
        self.vista_row = Adw.SwitchRow()
        self.vista_row.set_title(_('Use Vista taskbar behavior'))
        self.vista_row.set_subtitle(_('Keep launchers separate from running apps'))
        self.vista_row.set_active(_read_use_launchers())
        self.vista_row.connect('notify::active', self._on_vista_toggled)
        self.group_expander.add_row(self.vista_row)

        self.group_group = Adw.PreferencesGroup()
        self.group_group.add(self.group_expander)
        self.add(self.group_group)

        self._highlight_btn()
        self._sync_group_visibility()

    def _draw(self, area, cr, w, h, style, position):
        draw_preview(area, cr, w, h, style, position)

    def _on_click(self, btn, style):
        if apply_style_and_position(style, self.win.current_position):
            if style == 'eleven':
                _write_group_apps(True)
                _write_use_launchers(True)
            self.win.current_style = style
            self.win._sync_all_highlights()
            self._sync_group_visibility()
            self.win._refresh_previews()
            self.win._toast(_('✓ Applied — {style} | {pos}').format(style=self._label(style), pos=_(POS_LABELS[self.win.current_position])))
        else:
            self.win._toast(_('✗ Failed to apply style'))

    def _on_group_toggled(self, switch_row, pspec):
        if self.win.current_style == 'classic':
            _write_group_apps(switch_row.get_active())
            self._sync_vista_visibility()

    def _on_vista_toggled(self, switch_row, pspec):
        if self.win.current_style == 'classic':
            _write_use_launchers(switch_row.get_active())

    def _highlight_btn(self):
        if self.win.current_style == 'eleven':
            self.btn11.add_css_class('suggested-action')
            self.btn10.remove_css_class('suggested-action')
        else:
            self.btn10.add_css_class('suggested-action')
            self.btn11.remove_css_class('suggested-action')

    def _on_pos_toggled(self, btn, pos):
        if not btn.get_active():
            if pos == self.win.current_position:
                btn.handler_block_by_func(self._on_pos_toggled)
                btn.set_active(True)
                btn.handler_unblock_by_func(self._on_pos_toggled)
            return
        for p, other in self.pos_btns.items():
            if p != pos:
                other.handler_block_by_func(self._on_pos_toggled)
                other.set_active(False)
                other.handler_unblock_by_func(self._on_pos_toggled)
        if apply_style_and_position(self.win.current_style, pos):
            self.win.current_position = pos
            self._highlight_pos()
            self.win._refresh_previews()
            self.win._toast(_('✓ Applied — {style} | {pos}').format(style=self._label(self.win.current_style), pos=_(POS_LABELS[pos])))
        else:
            self.win._toast(_('✗ Failed to apply position'))

    def _highlight_pos(self):
        for p, btn in self.pos_btns.items():
            btn.handler_block_by_func(self._on_pos_toggled)
            if p == self.win.current_position:
                btn.set_active(True)
                btn.add_css_class('suggested-action')
            else:
                btn.set_active(False)
                btn.remove_css_class('suggested-action')
            btn.handler_unblock_by_func(self._on_pos_toggled)

    def _sync_group_visibility(self):
        if self.win.current_style == 'classic':
            self.group_group.set_visible(True)
            self.group_expander.set_enable_expansion(True)
            self.group_row.set_active(_read_group_apps())
            self._sync_vista_visibility()
        else:
            self.group_group.set_visible(False)

    def _sync_vista_visibility(self):
        if _read_group_apps():
            self.vista_row.set_visible(False)
        else:
            self.vista_row.set_visible(True)
            self.vista_row.set_active(_read_use_launchers())

    @staticmethod
    def _label(s):
        return _('Windows 11') if s == 'eleven' else _('Classic')


# ── panel widgets page ───────────────────────────────────────────

WIDGETS = {
    'weather': (_('Show Weather'), 'simple-weather@romanlefler.com'),
    'network': (_('Show Network'), 'network-stats@gnome.noroadsleft.xyz'),
}


class PanelWidgetsPage(Adw.PreferencesPage):
    def __init__(self, win):
        super().__init__()
        self.win = win
        self._busy = False
        self.set_title(_('Panel Widgets'))
        self.set_icon_name('application-x-addon-symbolic')

        group = Adw.PreferencesGroup()
        group.set_title(_('Panel Indicators'))
        group.set_description(_('Toggle panel indicator widgets on or off.'))

        for key, (label, uuid) in WIDGETS.items():
            row = Adw.SwitchRow()
            row.set_title(_(label))
            row.set_subtitle(uuid)
            row.set_active(_ext_is_enabled(uuid))
            row.connect('notify::active', self._on_toggle, uuid, label)
            group.add(row)

        self.add(group)

    def _on_toggle(self, switch_row, pspec, uuid, label):
        if self._busy:
            return
        state = switch_row.get_active()
        action = 'enable' if state else 'disable'
        try:
            subprocess.run(['gnome-extensions', action, uuid], check=True)
            self.win._toast(_('✓ {label} — {action}d').format(label=label, action=action))
        except subprocess.CalledProcessError:
            self.win._toast(_('✗ Failed to {action} {label}').format(action=action, label=label))
            # revert switch
            self._busy = True
            switch_row.set_active(not state)
            self._busy = False


# ── advanced page ────────────────────────────────────────────────

class AdvancedPage(Adw.PreferencesPage):
    def __init__(self, win):
        super().__init__()
        self.win = win
        self.set_title(_('Advanced'))
        self.set_icon_name('preferences-other-symbolic')

        # ── Appearance group ──
        appear = Adw.PreferencesGroup()
        appear.set_title(_('Appearance'))

        row_wallpaper = Adw.ActionRow()
        row_wallpaper.set_title(_('Wallpaper'))
        btn_wallpaper = Gtk.Button(label=_('Open'))
        btn_wallpaper.set_valign(Gtk.Align.CENTER)
        btn_wallpaper.connect('clicked', lambda b: subprocess.Popen(
            ['gnome-control-center', 'background']))
        row_wallpaper.add_suffix(btn_wallpaper)
        appear.add(row_wallpaper)

        if shutil.which('gnome-tweaks'):
            row_tweaks = Adw.ActionRow()
            row_tweaks.set_title(_('Tweaks'))
            btn_tweaks = Gtk.Button(label=_('Open'))
            btn_tweaks.set_valign(Gtk.Align.CENTER)
            btn_tweaks.connect('clicked', lambda b: subprocess.Popen(['gnome-tweaks']))
            row_tweaks.add_suffix(btn_tweaks)
            appear.add(row_tweaks)

        self.add(appear)

        # ── Extension Settings group ──
        ext = Adw.PreferencesGroup()
        ext.set_title(_('Extension Settings'))
        ext.set_description(_('Open the preference windows of the panel extensions.'))

        ext_defs = [
            ('ArcMenu', 'arcmenu@arcmenu.com'),
            ('Dash-to-Panel', 'dash-to-panel@jderose9.github.com'),
            ('Simple Weather', 'simple-weather@romanlefler.com'),
            ('Network Stats', 'network-stats@gnome.noroadsleft.xyz'),
        ]
        self._ext_rows = []
        for title_key, uuid in ext_defs:
            row = Adw.ActionRow()
            row.set_title(_(title_key))
            btn = Gtk.Button(label=_('Open'))
            btn.set_valign(Gtk.Align.CENTER)
            btn.connect('clicked', lambda b, u=uuid: subprocess.Popen(
                ['gnome-extensions', 'prefs', u]))
            row.add_suffix(btn)
            ext.add(row)
            self._ext_rows.append((row, uuid))

        self.add(ext)
        self.connect('map', lambda w: self._refresh_ext_rows())

        # ── Danger Zone group ──
        danger = Adw.PreferencesGroup()
        danger.set_title(_('Danger Zone'))

        row_reset = Adw.ActionRow()
        row_reset.set_title(_('Reset desktop settings'))
        row_reset.set_subtitle(_('Restore all GNOME Shell settings to factory defaults'))
        btn_reset = Gtk.Button(label=_('Reset'))
        btn_reset.set_valign(Gtk.Align.CENTER)
        btn_reset.add_css_class('destructive-action')
        btn_reset.connect('clicked', self._on_reset)
        row_reset.add_suffix(btn_reset)
        danger.add(row_reset)

        self.add(danger)

    def _refresh_ext_rows(self):
        for row, uuid in self._ext_rows:
            row.set_visible(_ext_is_enabled(uuid))

    def _on_reset(self, btn):
        def do_reset():
            try:
                subprocess.run(['dconf', 'reset', '-f', '/org/gnome/shell/'], check=True)
                self.win._toast(_('✓ Desktop settings reset. Restart Shell to apply.'))
            except subprocess.CalledProcessError:
                self.win._toast(_('✗ Failed to reset'))

        dlg = Adw.MessageDialog.new(
            self.win,
            _('Reset desktop settings?'),
            _('This will reset all GNOME Shell settings to factory defaults.\n'
              'Your extensions, panel layout, and preferences will be lost.')
        )
        dlg.add_response('cancel', _('Cancel'))
        dlg.add_response('reset', _('Reset'))
        dlg.set_response_appearance('reset', Adw.ResponseAppearance.DESTRUCTIVE)
        dlg.set_default_response('cancel')
        dlg.connect('response', lambda d, resp: (d.destroy(), do_reset() if resp == 'reset' else None))
        dlg.present()


def _find_headerbar(widget):
    """Recursively walk the widget tree to find an Adw.HeaderBar."""
    if isinstance(widget, Adw.HeaderBar):
        return widget
    child = widget.get_first_child() if hasattr(widget, 'get_first_child') else None
    while child:
        result = _find_headerbar(child)
        if result:
            return result
        child = child.get_next_sibling() if hasattr(child, 'get_next_sibling') else None
    return None


# ── GDM wallpaper page ────────────────────────────────────────────

GDM_OUTPUT = '/var/lib/anduinos-gdm3-wallpaper/anduinos-theme.gresource'


class GdmWallpaperPage(Adw.PreferencesPage):
    def __init__(self, win):
        super().__init__()
        self.win = win
        self._wallpaper_path = ''
        self.set_title(_('GDM Wallpaper'))
        self.set_icon_name('preferences-desktop-wallpaper-symbolic')

        # ── Choose wallpaper ──
        choose = Adw.PreferencesGroup()
        choose.set_title(_('Login Screen Wallpaper'))
        choose.set_description(_('Choose an image for the GDM login screen background.'))

        self.preview_img = Gtk.Picture()
        self.preview_img.set_size_request(340, 190)
        self.preview_img.set_content_fit(Gtk.ContentFit.COVER)
        self.preview_img.add_css_class('card')
        self.preview_img.set_visible(False)
        choose.add(self.preview_img)

        btn_row = Adw.ActionRow()
        btn_row.set_title(_('Select image'))
        btn = Gtk.Button(label=_('Choose…'))
        btn.set_valign(Gtk.Align.CENTER)
        btn.connect('clicked', self._on_choose)
        btn_row.add_suffix(btn)
        choose.add(btn_row)
        self.add(choose)

        # ── Apply ──
        apply_grp = Adw.PreferencesGroup()
        apply_btn = Gtk.Button(label=_('Apply to Login Screen'))
        apply_btn.add_css_class('suggested-action')
        apply_btn.set_halign(Gtk.Align.CENTER)
        apply_btn.set_margin_top(12)
        apply_btn.set_margin_bottom(12)
        apply_btn.connect('clicked', self._on_apply)
        apply_grp.add(apply_btn)
        self.add(apply_grp)

    def _on_choose(self, btn):
        dialog = Gtk.FileDialog.new()
        flt = Gtk.FileFilter()
        flt.set_name(_('Images'))
        for mime in ['image/png', 'image/jpeg']:
            flt.add_mime_type(mime)
        filters = Gio.ListStore.new(Gtk.FileFilter)
        filters.append(flt)
        dialog.set_filters(filters)
        dialog.open(self.win, None, self._on_file_chosen)

    def _on_file_chosen(self, dialog, result):
        try:
            f = dialog.open_finish(result)
            if f:
                self._wallpaper_path = f.get_path()
                self.preview_img.set_filename(self._wallpaper_path)
                self.preview_img.set_visible(True)
        except GLib.Error as e:
            if not e.matches(Gio.io_error_quark(), Gio.IOErrorEnum.CANCELLED):
                print(f"File dialog error: {e.message}", file=sys.stderr)

    def _on_apply(self, btn):
        if not self._wallpaper_path:
            self.win._toast(_('✗ Please select an image first'))
            return
        btn.set_sensitive(False)
        proc = Gio.Subprocess.new(
            ['pkexec', 'anduinos-gdm-set-wallpaper',
             '--wallpaper', self._wallpaper_path,
             '--output', GDM_OUTPUT],
            Gio.SubprocessFlags.NONE,
        )
        proc.wait_async(None, self._on_apply_done, btn)

    def _on_apply_done(self, proc, result, btn):
        ok = proc.wait_finish(result)
        btn.set_sensitive(True)
        if ok:
            self.win._toast(_('✓ GDM wallpaper applied'))
        else:
            self.win._toast(_('✗ Failed to set GDM wallpaper'))


# ── main window ──────────────────────────────────────────────────

class AppearanceWindow(Adw.PreferencesWindow):
    def __init__(self, app):
        super().__init__(application=app)
        self.set_title(_('AnduinOS Appearance'))
        self.set_default_size(780, 560)
        self.set_icon_name('anduinos-appearance')

        self.current_style, self.current_position = detect_current()

        # Pages — added in sidebar order
        self.style_page = StylePage(self)
        self.widgets_page = PanelWidgetsPage(self)
        self.gdm_page = GdmWallpaperPage(self)
        self.advanced_page = AdvancedPage(self)

        self.add(self.style_page)
        self.add(self.widgets_page)
        self.add(self.gdm_page)
        self.add(self.advanced_page)

        # Inject hamburger menu into internal headerbar on realize
        self.connect('realize', self._inject_menu)

    def _inject_menu(self, window):
        """Find the internal Adw.HeaderBar and add an About menu button."""
        menu = Gio.Menu()
        menu.append(_('About AnduinOS Appearance'), 'app.about')

        menu_btn = Gtk.MenuButton()
        menu_btn.set_icon_name('open-menu-symbolic')
        menu_btn.set_menu_model(menu)

        header = _find_headerbar(window)
        if header:
            header.pack_end(menu_btn)

    def _toast(self, message: str):
        self.add_toast(Adw.Toast.new(message))

    def _sync_all_highlights(self):
        self.style_page._highlight_btn()
        self.style_page._highlight_pos()

    def _refresh_previews(self):
        for page in [self.style_page]:
            page.preview.set_draw_func(page._draw, self.current_style, self.current_position)
            page.preview.queue_draw()


# ── application ──────────────────────────────────────────────────

class AppearanceApp(Adw.Application):
    def __init__(self):
        super().__init__(application_id='com.anduinos.Appearance')

    def do_startup(self):
        Adw.Application.do_startup(self)
        about_action = Gio.SimpleAction.new('about', None)
        about_action.connect('activate', self._on_about)
        self.add_action(about_action)

    def _on_about(self, action, param):
        dialog = Adw.AboutDialog()
        dialog.set_application_name(_('AnduinOS Appearance'))
        dialog.set_application_icon('anduinos-appearance')
        dialog.set_developer_name(_('AnduinOS Team'))
        dialog.set_website('https://www.anduinos.com')
        dialog.set_issue_url('https://github.com/AiursoftWeb/AnduinOS-Packages/issues')
        dialog.set_license_type(Gtk.License.GPL_3_0)
        dialog.set_version('2.0.0 Beta 3')
        dialog.present(self.get_active_window())

    def do_activate(self):
        win = AppearanceWindow(self)
        win.present()


def main():
    return AppearanceApp().run(None)


if __name__ == '__main__':
    raise SystemExit(main())
