/*
 * DWN - Desktop Window Manager
 * retoor <retoor@molodetz.nl>
 * System tray widgets implementation with UTF-8 support
 */

#include "systray.h"
#include "panel.h"
#include "config.h"
#include "util.h"
#include "notifications.h"
#include "atoms.h"
#include "api.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <pthread.h>

#define ICON_WIFI_FULL      "\xE2\x96\x82\xE2\x96\x84\xE2\x96\x86\xE2\x96\x88"
#define ICON_WIFI_OFF       "\xE2\x9C\x97"
#define ICON_VOLUME_HIGH    "\xF0\x9F\x94\x8A"
#define ICON_VOLUME_MED     "\xF0\x9F\x94\x89"
#define ICON_VOLUME_LOW     "\xF0\x9F\x94\x88"
#define ICON_VOLUME_MUTE    "\xF0\x9F\x94\x87"
#define ICON_BATTERY_FULL   "\xF0\x9F\x94\x8B"
#define ICON_BATTERY_CHARGE "\xE2\x9A\xA1"

#define ASCII_WIFI_ON       "W"
#define ASCII_WIFI_OFF      "-"
#define ASCII_VOL_HIGH      "V"
#define ASCII_VOL_MUTE      "M"
#define ASCII_BATTERY       "B"
#define ASCII_CHARGING      "+"

#define SYSTRAY_SPACING 12
#define SLIDER_WIDTH 30
#define SLIDER_HEIGHT 120
#define SLIDER_PADDING 8
#define SLIDER_KNOB_HEIGHT 8

WifiState wifi_state = {0};
AudioState audio_state = {0};
BatteryState battery_state = {0};
MultiBatteryState multi_battery_state = {0};
VolumeSlider *volume_slider = NULL;

/* Fade controls */
FadeControl fade_speed_control = {0};
FadeControl fade_intensity_control = {0};
static int fade_speed_icon_x = 0;
static int fade_intensity_icon_x = 0;

TrayIcon tray_icons[MAX_TRAY_ICONS];
int tray_icon_count = 0;
Window tray_selection_owner = None;

static pthread_t update_thread;
static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;
static volatile int thread_running = 0;

static int systray_x = 0;
static int systray_width = 0;
static int wifi_icon_x = 0;
static int audio_icon_x = 0;
static int xembed_icons_x = 0;

static void wifi_update_state_internal(void);
static void audio_update_state_internal(void);

static XftDraw *systray_xft_draw = NULL;
static Drawable systray_xft_drawable = None;
static Visual *systray_cached_visual = NULL;

#define SYSTRAY_XFT_COLOR_CACHE_SIZE 8

typedef struct {
    unsigned long pixel;
    XftColor xft_color;
    bool valid;
    int lru_counter;
} SystrayCachedColor;

static SystrayCachedColor systray_color_cache[SYSTRAY_XFT_COLOR_CACHE_SIZE];
static int systray_color_lru_counter = 0;

static XftDraw *systray_get_xft_draw(Drawable d)
{
    if (systray_xft_drawable == d && systray_xft_draw != NULL) {
        return systray_xft_draw;
    }
    if (systray_xft_draw != NULL) {
        XftDrawDestroy(systray_xft_draw);
    }
    if (systray_cached_visual == NULL) {
        systray_cached_visual = DefaultVisual(dwn->display, dwn->screen);
    }
    systray_xft_draw = XftDrawCreate(dwn->display, d, systray_cached_visual, dwn->colormap);
    systray_xft_drawable = d;
    return systray_xft_draw;
}

static XftColor *systray_get_cached_color(unsigned long color)
{
    if (systray_cached_visual == NULL) {
        systray_cached_visual = DefaultVisual(dwn->display, dwn->screen);
    }

    for (int i = 0; i < SYSTRAY_XFT_COLOR_CACHE_SIZE; i++) {
        if (systray_color_cache[i].valid && systray_color_cache[i].pixel == color) {
            systray_color_cache[i].lru_counter = ++systray_color_lru_counter;
            return &systray_color_cache[i].xft_color;
        }
    }

    int oldest_idx = 0;
    int oldest_lru = systray_color_cache[0].lru_counter;
    for (int i = 1; i < SYSTRAY_XFT_COLOR_CACHE_SIZE; i++) {
        if (!systray_color_cache[i].valid) {
            oldest_idx = i;
            break;
        }
        if (systray_color_cache[i].lru_counter < oldest_lru) {
            oldest_lru = systray_color_cache[i].lru_counter;
            oldest_idx = i;
        }
    }

    if (systray_color_cache[oldest_idx].valid) {
        XftColorFree(dwn->display, systray_cached_visual, dwn->colormap,
                     &systray_color_cache[oldest_idx].xft_color);
    }

    XRenderColor render_color;
    render_color.red = ((color >> 16) & 0xFF) * 257;
    render_color.green = ((color >> 8) & 0xFF) * 257;
    render_color.blue = (color & 0xFF) * 257;
    render_color.alpha = 0xFFFF;

    XftColorAllocValue(dwn->display, systray_cached_visual, dwn->colormap,
                       &render_color, &systray_color_cache[oldest_idx].xft_color);
    systray_color_cache[oldest_idx].pixel = color;
    systray_color_cache[oldest_idx].valid = true;
    systray_color_cache[oldest_idx].lru_counter = ++systray_color_lru_counter;

    return &systray_color_cache[oldest_idx].xft_color;
}

static void draw_utf8_text(Drawable d, int x, int y, const char *text, unsigned long color)
{
    if (dwn == NULL || dwn->display == NULL || text == NULL) {
        return;
    }

    if (dwn->xft_font != NULL) {
        XftDraw *xft_draw = systray_get_xft_draw(d);
        if (xft_draw != NULL) {
            XftColor *xft_color = systray_get_cached_color(color);
            XftDrawStringUtf8(xft_draw, xft_color, dwn->xft_font,
                              x, y, (const FcChar8 *)text, strlen(text));
            return;
        }
    }

    XSetForeground(dwn->display, dwn->gc, color);
    XDrawString(dwn->display, d, dwn->gc, x, y, text, strlen(text));
}

static int get_text_width(const char *text)
{
    if (dwn == NULL || text == NULL) {
        return 0;
    }

    if (dwn->xft_font != NULL) {
        XGlyphInfo extents;
        XftTextExtentsUtf8(dwn->display, dwn->xft_font,
                           (const FcChar8 *)text, strlen(text), &extents);
        return extents.xOff;
    }

    if (dwn->font != NULL) {
        return XTextWidth(dwn->font, text, strlen(text));
    }

    return strlen(text) * 8;
}


static void *systray_update_thread(void *arg)
{
    (void)arg;

    while (thread_running) {
        wifi_update_state_internal();

        audio_update_state_internal();

        pthread_mutex_lock(&state_mutex);
        battery_update_state();
        pthread_mutex_unlock(&state_mutex);

        for (int i = 0; i < 20 && thread_running; i++) {
            usleep(100000);
        }
    }

    return NULL;
}

void systray_init(void)
{
    memset(&wifi_state, 0, sizeof(wifi_state));
    memset(&audio_state, 0, sizeof(audio_state));
    memset(&battery_state, 0, sizeof(battery_state));

    audio_state.volume = 50;
    wifi_state.enabled = true;

    thread_running = 1;
    if (pthread_create(&update_thread, NULL, systray_update_thread, NULL) != 0) {
        LOG_WARN("Failed to create systray update thread");
        thread_running = 0;
    }

    LOG_INFO("System tray initialized");
}

void systray_cleanup(void)
{
    if (thread_running) {
        thread_running = 0;
        pthread_join(update_thread, NULL);
    }

    if (volume_slider != NULL) {
        volume_slider_destroy(volume_slider);
        volume_slider = NULL;
    }
}


void battery_update_state(void)
{
    DIR *dir;
    struct dirent *entry;
    char path[512];
    char value[64];
    FILE *fp;

    memset(&multi_battery_state, 0, sizeof(multi_battery_state));
    battery_state.present = false;
    battery_state.charging = false;
    battery_state.percentage = 0;

    dir = opendir("/sys/class/power_supply");
    if (dir == NULL) {
        return;
    }

    unsigned long total_energy_now = 0;
    unsigned long total_energy_full = 0;
    bool any_charging = false;

    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_name[0] == '.') {
            continue;
        }

        if (multi_battery_state.count >= MAX_BATTERIES) {
            break;
        }

        snprintf(path, sizeof(path), "/sys/class/power_supply/%s/type", entry->d_name);
        fp = fopen(path, "r");
        if (fp != NULL) {
            if (fgets(value, sizeof(value), fp) != NULL) {
                value[strcspn(value, "\n")] = '\0';
                if (strcmp(value, "Battery") == 0) {
                    SingleBattery *bat = &multi_battery_state.batteries[multi_battery_state.count];
                    bat->present = true;
                    size_t name_len = strlen(entry->d_name);
                    if (name_len >= sizeof(bat->name)) {
                        name_len = sizeof(bat->name) - 1;
                    }
                    memcpy(bat->name, entry->d_name, name_len);
                    bat->name[name_len] = '\0';

                    snprintf(path, sizeof(path), "/sys/class/power_supply/%s/energy_now", entry->d_name);
                    FILE *energy_fp = fopen(path, "r");
                    if (energy_fp != NULL) {
                        if (fgets(value, sizeof(value), energy_fp) != NULL) {
                            bat->energy_now = strtoul(value, NULL, 10);
                        }
                        fclose(energy_fp);
                    } else {
                        snprintf(path, sizeof(path), "/sys/class/power_supply/%s/charge_now", entry->d_name);
                        energy_fp = fopen(path, "r");
                        if (energy_fp != NULL) {
                            if (fgets(value, sizeof(value), energy_fp) != NULL) {
                                bat->energy_now = strtoul(value, NULL, 10);
                            }
                            fclose(energy_fp);
                        }
                    }

                    snprintf(path, sizeof(path), "/sys/class/power_supply/%s/energy_full", entry->d_name);
                    energy_fp = fopen(path, "r");
                    if (energy_fp != NULL) {
                        if (fgets(value, sizeof(value), energy_fp) != NULL) {
                            bat->energy_full = strtoul(value, NULL, 10);
                        }
                        fclose(energy_fp);
                    } else {
                        snprintf(path, sizeof(path), "/sys/class/power_supply/%s/charge_full", entry->d_name);
                        energy_fp = fopen(path, "r");
                        if (energy_fp != NULL) {
                            if (fgets(value, sizeof(value), energy_fp) != NULL) {
                                bat->energy_full = strtoul(value, NULL, 10);
                            }
                            fclose(energy_fp);
                        }
                    }

                    snprintf(path, sizeof(path), "/sys/class/power_supply/%s/capacity", entry->d_name);
                    FILE *cap_fp = fopen(path, "r");
                    if (cap_fp != NULL) {
                        if (fgets(value, sizeof(value), cap_fp) != NULL) {
                            bat->percentage = atoi(value);
                        }
                        fclose(cap_fp);
                    }

                    snprintf(path, sizeof(path), "/sys/class/power_supply/%s/status", entry->d_name);
                    FILE *status_fp = fopen(path, "r");
                    if (status_fp != NULL) {
                        if (fgets(value, sizeof(value), status_fp) != NULL) {
                            value[strcspn(value, "\n")] = '\0';
                            bat->charging = (strcmp(value, "Charging") == 0 ||
                                             strcmp(value, "Full") == 0);
                            if (bat->charging) {
                                any_charging = true;
                            }
                        }
                        fclose(status_fp);
                    }

                    total_energy_now += bat->energy_now;
                    total_energy_full += bat->energy_full;
                    multi_battery_state.count++;
                }
            }
            fclose(fp);
        }
    }
    closedir(dir);

    multi_battery_state.any_charging = any_charging;

    if (total_energy_full > 0) {
        multi_battery_state.combined_percentage = (int)((total_energy_now * 100) / total_energy_full);
    } else if (multi_battery_state.count > 0) {
        int total_pct = 0;
        for (int i = 0; i < multi_battery_state.count; i++) {
            total_pct += multi_battery_state.batteries[i].percentage;
        }
        multi_battery_state.combined_percentage = total_pct / multi_battery_state.count;
    }

    if (multi_battery_state.count > 0) {
        battery_state.present = true;
        battery_state.charging = any_charging;
        battery_state.percentage = multi_battery_state.combined_percentage;
    }
}

const char *battery_get_icon(void)
{
    if (!battery_state.present) {
        return "";
    }

    if (battery_state.charging) {
        return ASCII_CHARGING;
    }

    return ASCII_BATTERY;
}


static void wifi_update_state_internal(void)
{
    FILE *fp;
    char line[256];
    WifiState temp_state = {0};

    fp = popen("nmcli -t -f GENERAL.STATE,GENERAL.CONNECTION dev show 2>/dev/null | head -2", "r");
    if (fp != NULL) {
        while (fgets(line, sizeof(line), fp) != NULL) {
            line[strcspn(line, "\n")] = '\0';

            if (strstr(line, "GENERAL.STATE:") != NULL) {
                if (strstr(line, "100 (connected)") != NULL) {
                    temp_state.connected = true;
                    temp_state.enabled = true;
                }
            }
            if (strstr(line, "GENERAL.CONNECTION:") != NULL) {
                char *ssid = strchr(line, ':');
                if (ssid != NULL && strlen(ssid) > 1) {
                    ssid++;
                    strncpy(temp_state.current_ssid, ssid, sizeof(temp_state.current_ssid) - 1);
                }
            }
        }
        pclose(fp);
    }

    if (temp_state.connected) {
        fp = popen("nmcli -t -f IN-USE,SIGNAL dev wifi list 2>/dev/null | grep '^\\*' | cut -d: -f2", "r");
        if (fp != NULL) {
            if (fgets(line, sizeof(line), fp) != NULL) {
                temp_state.signal_strength = atoi(line);
            }
            pclose(fp);
        }
    }

    pthread_mutex_lock(&state_mutex);
    wifi_state.connected = temp_state.connected;
    wifi_state.enabled = temp_state.enabled;
    wifi_state.signal_strength = temp_state.signal_strength;
    memcpy(wifi_state.current_ssid, temp_state.current_ssid, sizeof(wifi_state.current_ssid));
    pthread_mutex_unlock(&state_mutex);
}

void wifi_update_state(void)
{
}

const char *wifi_get_icon(void)
{
    if (!wifi_state.enabled || !wifi_state.connected) {
        return ASCII_WIFI_OFF;
    }
    return ASCII_WIFI_ON;
}


static void audio_update_state_internal(void)
{
    FILE *fp;
    char line[256];
    int volume = 50;
    bool muted = false;

    fp = popen("amixer get Master 2>/dev/null | grep -o '[0-9]*%\\|\\[on\\]\\|\\[off\\]' | head -2", "r");
    if (fp != NULL) {
        while (fgets(line, sizeof(line), fp) != NULL) {
            line[strcspn(line, "\n")] = '\0';

            if (strchr(line, '%') != NULL) {
                volume = atoi(line);
            } else if (strcmp(line, "[off]") == 0) {
                muted = true;
            } else if (strcmp(line, "[on]") == 0) {
                muted = false;
            }
        }
        pclose(fp);
    }

    pthread_mutex_lock(&state_mutex);
    audio_state.volume = volume;
    audio_state.muted = muted;
    pthread_mutex_unlock(&state_mutex);
}

void audio_update_state(void)
{
}

void audio_set_volume(int volume)
{
    if (volume < 0) volume = 0;
    if (volume > 100) volume = 100;

    char cmd[64];
    snprintf(cmd, sizeof(cmd), "amixer set Master %d%% >/dev/null 2>&1 &", volume);
    spawn_async(cmd);

    pthread_mutex_lock(&state_mutex);
    audio_state.volume = volume;
    audio_state.muted = false;
    pthread_mutex_unlock(&state_mutex);
}

void audio_toggle_mute(void)
{
    spawn_async("amixer set Master toggle >/dev/null 2>&1 &");

    pthread_mutex_lock(&state_mutex);
    audio_state.muted = !audio_state.muted;
    pthread_mutex_unlock(&state_mutex);
}

const char *audio_get_icon(void)
{
    if (audio_state.muted || audio_state.volume == 0) {
        return ASCII_VOL_MUTE;
    }
    return ASCII_VOL_HIGH;
}


VolumeSlider *volume_slider_create(int x, int y)
{
    if (dwn == NULL || dwn->display == NULL) {
        return NULL;
    }

    VolumeSlider *slider = dwn_calloc(1, sizeof(VolumeSlider));
    slider->x = x;
    slider->y = y;
    slider->width = SLIDER_WIDTH;
    slider->height = SLIDER_HEIGHT;
    slider->visible = false;
    slider->dragging = false;

    return slider;
}

void volume_slider_destroy(VolumeSlider *slider)
{
    if (slider == NULL) {
        return;
    }

    if (slider->window != None && dwn != NULL && dwn->display != NULL) {
        XDestroyWindow(dwn->display, slider->window);
    }

    dwn_free(slider);
}

void volume_slider_show(VolumeSlider *slider)
{
    if (slider == NULL || dwn == NULL || dwn->display == NULL) {
        return;
    }

    if (slider->window == None) {
        XSetWindowAttributes swa;
        swa.override_redirect = True;
        swa.background_pixel = dwn->config->colors.panel_bg;
        swa.border_pixel = dwn->config->colors.border_focused;
        swa.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask |
                         PointerMotionMask | LeaveWindowMask;

        slider->window = XCreateWindow(dwn->display, dwn->root,
                                        slider->x, slider->y,
                                        slider->width, slider->height,
                                        0,
                                        CopyFromParent, InputOutput, CopyFromParent,
                                        CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask,
                                        &swa);
    }

    slider->visible = true;
    XMapRaised(dwn->display, slider->window);
    volume_slider_render(slider);
}

void volume_slider_hide(VolumeSlider *slider)
{
    if (slider == NULL || slider->window == None) {
        return;
    }

    slider->visible = false;
    slider->dragging = false;
    XUnmapWindow(dwn->display, slider->window);
}

void volume_slider_render(VolumeSlider *slider)
{
    if (slider == NULL || slider->window == None || !slider->visible) {
        return;
    }

    Display *dpy = dwn->display;
    const ColorScheme *colors = config_get_colors();

    XSetForeground(dpy, dwn->gc, colors->panel_bg);
    XFillRectangle(dpy, slider->window, dwn->gc, 0, 0, slider->width, slider->height);

    int track_x = slider->width / 2 - 2;
    int track_y = SLIDER_PADDING;
    int track_height = slider->height - 2 * SLIDER_PADDING;

    XSetForeground(dpy, dwn->gc, colors->workspace_inactive);
    XFillRectangle(dpy, slider->window, dwn->gc, track_x, track_y, 4, track_height);

    int fill_height = (audio_state.volume * track_height) / 100;
    int fill_y = track_y + track_height - fill_height;

    XSetForeground(dpy, dwn->gc, colors->workspace_active);
    XFillRectangle(dpy, slider->window, dwn->gc, track_x, fill_y, 4, fill_height);

    int knob_y = fill_y - SLIDER_KNOB_HEIGHT / 2;
    if (knob_y < track_y) knob_y = track_y;
    if (knob_y > track_y + track_height - SLIDER_KNOB_HEIGHT) {
        knob_y = track_y + track_height - SLIDER_KNOB_HEIGHT;
    }

    XSetForeground(dpy, dwn->gc, colors->panel_fg);
    XFillRectangle(dpy, slider->window, dwn->gc,
                   track_x - 4, knob_y, 12, SLIDER_KNOB_HEIGHT);

    char vol_text[8];
    snprintf(vol_text, sizeof(vol_text), "%d%%", audio_state.volume);
    int text_width = get_text_width(vol_text);
    int text_x = (slider->width - text_width) / 2;

    draw_utf8_text(slider->window, text_x, slider->height - 4, vol_text,
                   ambient_glow_bg(colors->panel_fg, dwn->ambient_phase + PHASE_OFFSET_SYSTRAY));

    XFlush(dpy);
}

void volume_slider_handle_click(VolumeSlider *slider, int x, int y)
{
    (void)x;

    if (slider == NULL || !slider->visible) {
        return;
    }

    slider->dragging = true;

    int track_y = SLIDER_PADDING;
    int track_height = slider->height - 2 * SLIDER_PADDING;

    int relative_y = y - track_y;
    if (relative_y < 0) relative_y = 0;
    if (relative_y > track_height) relative_y = track_height;

    int new_volume = 100 - (relative_y * 100 / track_height);
    audio_set_volume(new_volume);
    volume_slider_render(slider);
    panel_render_all();
}

void volume_slider_handle_motion(VolumeSlider *slider, int x, int y)
{
    if (slider == NULL || !slider->visible || !slider->dragging) {
        return;
    }

    volume_slider_handle_click(slider, x, y);
}

void volume_slider_handle_release(VolumeSlider *slider)
{
    if (slider == NULL) {
        return;
    }
    slider->dragging = false;
}


int systray_get_width(void)
{
    pthread_mutex_lock(&state_mutex);
    AudioState audio_snap = audio_state;
    WifiState wifi_snap;
    wifi_snap.connected = wifi_state.connected;
    wifi_snap.enabled = wifi_state.enabled;
    wifi_snap.signal_strength = wifi_state.signal_strength;
    memcpy(wifi_snap.current_ssid, wifi_state.current_ssid, sizeof(wifi_snap.current_ssid));
    pthread_mutex_unlock(&state_mutex);

    char audio_label[32];
    const char *audio_icon = (audio_snap.muted || audio_snap.volume == 0) ? ASCII_VOL_MUTE : ASCII_VOL_HIGH;
    snprintf(audio_label, sizeof(audio_label), "%s%d%%", audio_icon, audio_snap.volume);

    char wifi_label[128];
    const char *wifi_icon_str = (!wifi_snap.enabled || !wifi_snap.connected) ? ASCII_WIFI_OFF : ASCII_WIFI_ON;
    if (wifi_snap.connected && wifi_snap.current_ssid[0] != '\0') {
        snprintf(wifi_label, sizeof(wifi_label), "%s %s %d%%",
                 wifi_icon_str, wifi_snap.current_ssid, wifi_snap.signal_strength);
    } else {
        snprintf(wifi_label, sizeof(wifi_label), "%s", wifi_icon_str);
    }

    int builtin_width = get_text_width(audio_label) + SYSTRAY_SPACING + get_text_width(wifi_label);
    int xembed_width = xembed_get_icons_width();

    if (xembed_width > 0) {
        return xembed_width + SYSTRAY_SPACING + builtin_width;
    }
    return builtin_width;
}

void systray_render(Panel *panel, int x, int *width)
{
    if (panel == NULL || width == NULL) {
        return;
    }

    const ColorScheme *colors = config_get_colors();

    pthread_mutex_lock(&state_mutex);
    AudioState audio_snap = audio_state;
    WifiState wifi_snap;
    wifi_snap.connected = wifi_state.connected;
    wifi_snap.enabled = wifi_state.enabled;
    wifi_snap.signal_strength = wifi_state.signal_strength;
    memcpy(wifi_snap.current_ssid, wifi_state.current_ssid, sizeof(wifi_snap.current_ssid));
    pthread_mutex_unlock(&state_mutex);

    int font_height = 12;
    if (dwn->xft_font != NULL) {
        font_height = dwn->xft_font->ascent;
    } else if (dwn->font != NULL) {
        font_height = dwn->font->ascent;
    }
    int text_y = (panel->height + font_height) / 2;

    systray_x = x;
    int current_x = x;

    int xembed_width = xembed_get_icons_width();
    if (xembed_width > 0) {
        xembed_render_icons(panel, current_x);
        current_x += xembed_width + SYSTRAY_SPACING;
    }

    audio_icon_x = current_x;
    char audio_label[32];
    const char *audio_icon = (audio_snap.muted || audio_snap.volume == 0) ? ASCII_VOL_MUTE : ASCII_VOL_HIGH;
    snprintf(audio_label, sizeof(audio_label), "%s%d%%", audio_icon, audio_snap.volume);

    unsigned long audio_color = audio_snap.muted ? colors->workspace_inactive : colors->panel_fg;
    audio_color = ambient_glow_bg(audio_color, dwn->ambient_phase + PHASE_OFFSET_SYSTRAY);
    draw_utf8_text(panel->buffer, current_x, text_y, audio_label, audio_color);
    current_x += get_text_width(audio_label) + SYSTRAY_SPACING;

    wifi_icon_x = current_x;
    char wifi_label[128];
    const char *wifi_icon_str = (!wifi_snap.enabled || !wifi_snap.connected) ? ASCII_WIFI_OFF : ASCII_WIFI_ON;

    if (wifi_snap.connected && wifi_snap.current_ssid[0] != '\0') {
        snprintf(wifi_label, sizeof(wifi_label), "%s %s %d%%",
                 wifi_icon_str, wifi_snap.current_ssid, wifi_snap.signal_strength);
    } else {
        snprintf(wifi_label, sizeof(wifi_label), "%s", wifi_icon_str);
    }

    unsigned long wifi_color = wifi_snap.connected ? colors->panel_fg : colors->workspace_inactive;
    wifi_color = ambient_glow_bg(wifi_color, dwn->ambient_phase + PHASE_OFFSET_SYSTRAY);
    draw_utf8_text(panel->buffer, current_x, text_y, wifi_label, wifi_color);
    current_x += get_text_width(wifi_label);

    systray_width = current_x - x;
    *width = systray_width;
}

int systray_hit_test(int x)
{
    int xembed_idx = xembed_hit_test(x);
    if (xembed_idx >= 0) {
        return 2;
    }
    if (x >= wifi_icon_x) {
        return 0;
    }
    if (x >= audio_icon_x && x < wifi_icon_x) {
        return 1;
    }
    return -1;
}

void systray_handle_click(int x, int y, int button)
{
    int widget = systray_hit_test(x);

    if (widget == 2) {
        xembed_handle_click(x, y, button);
        return;
    }

    if (widget == 1) {
        if (button == 1) {
            if (volume_slider == NULL) {
                int panel_height = config_get_panel_height();
                volume_slider = volume_slider_create(audio_icon_x, panel_height);
            }

            if (volume_slider->visible) {
                volume_slider_hide(volume_slider);
            } else {
                volume_slider->x = audio_icon_x;
                volume_slider->y = config_get_panel_height();
                volume_slider_show(volume_slider);
            }
        } else if (button == 3) {
            audio_toggle_mute();
            panel_render_all();
        } else if (button == 4) {
            audio_set_volume(audio_state.volume + 5);
            if (volume_slider != NULL && volume_slider->visible) {
                volume_slider_render(volume_slider);
            }
            panel_render_all();
        } else if (button == 5) {
            audio_set_volume(audio_state.volume - 5);
            if (volume_slider != NULL && volume_slider->visible) {
                volume_slider_render(volume_slider);
            }
            panel_render_all();
        }
    }

    (void)y;
}

void systray_update(void)
{
}


void systray_lock(void)
{
    pthread_mutex_lock(&state_mutex);
}

void systray_unlock(void)
{
    pthread_mutex_unlock(&state_mutex);
}

BatteryState systray_get_battery_snapshot(void)
{
    BatteryState snapshot;
    pthread_mutex_lock(&state_mutex);
    snapshot = battery_state;
    pthread_mutex_unlock(&state_mutex);
    return snapshot;
}

MultiBatteryState systray_get_multi_battery_snapshot(void)
{
    MultiBatteryState snapshot;
    pthread_mutex_lock(&state_mutex);
    snapshot = multi_battery_state;
    pthread_mutex_unlock(&state_mutex);
    return snapshot;
}

AudioState systray_get_audio_snapshot(void)
{
    AudioState snapshot;
    pthread_mutex_lock(&state_mutex);
    snapshot = audio_state;
    pthread_mutex_unlock(&state_mutex);
    return snapshot;
}


void xembed_init(void)
{
    if (dwn == NULL || dwn->display == NULL) {
        return;
    }

    Display *dpy = dwn->display;

    memset(tray_icons, 0, sizeof(tray_icons));
    tray_icon_count = 0;

    tray_selection_owner = XCreateSimpleWindow(dpy, dwn->root, -1, -1, 1, 1, 0, 0, 0);
    if (tray_selection_owner == None) {
        LOG_WARN("Failed to create tray selection owner window");
        return;
    }

    Window existing = XGetSelectionOwner(dpy, ewmh.NET_SYSTEM_TRAY_S0);
    if (existing != None) {
        LOG_WARN("Another system tray is already running");
        XDestroyWindow(dpy, tray_selection_owner);
        tray_selection_owner = None;
        return;
    }

    XSetSelectionOwner(dpy, ewmh.NET_SYSTEM_TRAY_S0, tray_selection_owner, CurrentTime);
    if (XGetSelectionOwner(dpy, ewmh.NET_SYSTEM_TRAY_S0) != tray_selection_owner) {
        LOG_WARN("Failed to acquire system tray selection");
        XDestroyWindow(dpy, tray_selection_owner);
        tray_selection_owner = None;
        return;
    }

    long orientation = 0;
    XChangeProperty(dpy, tray_selection_owner, ewmh.NET_SYSTEM_TRAY_ORIENTATION,
                    XA_CARDINAL, 32, PropModeReplace,
                    (unsigned char *)&orientation, 1);

    VisualID visual_id = XVisualIDFromVisual(DefaultVisual(dpy, dwn->screen));
    XChangeProperty(dpy, tray_selection_owner, ewmh.NET_SYSTEM_TRAY_VISUAL,
                    XA_VISUALID, 32, PropModeReplace,
                    (unsigned char *)&visual_id, 1);

    XEvent ev;
    memset(&ev, 0, sizeof(ev));
    ev.xclient.type = ClientMessage;
    ev.xclient.window = dwn->root;
    ev.xclient.message_type = ewmh.MANAGER;
    ev.xclient.format = 32;
    ev.xclient.data.l[0] = CurrentTime;
    ev.xclient.data.l[1] = ewmh.NET_SYSTEM_TRAY_S0;
    ev.xclient.data.l[2] = tray_selection_owner;
    ev.xclient.data.l[3] = 0;
    ev.xclient.data.l[4] = 0;

    XSendEvent(dpy, dwn->root, False, StructureNotifyMask, &ev);
    XFlush(dpy);

    LOG_INFO("XEmbed system tray initialized");
}

void xembed_cleanup(void)
{
    if (dwn == NULL || dwn->display == NULL) {
        return;
    }

    for (int i = 0; i < tray_icon_count; i++) {
        if (tray_icons[i].window != None) {
            XReparentWindow(dwn->display, tray_icons[i].window, dwn->root, 0, 0);
        }
    }

    tray_icon_count = 0;

    if (tray_selection_owner != None) {
        XDestroyWindow(dwn->display, tray_selection_owner);
        tray_selection_owner = None;
    }

    LOG_DEBUG("XEmbed system tray cleaned up");
}

void xembed_send_message(Window icon, long message, long detail, long data1, long data2)
{
    if (dwn == NULL || dwn->display == NULL || icon == None) {
        return;
    }

    XEvent ev;
    memset(&ev, 0, sizeof(ev));
    ev.xclient.type = ClientMessage;
    ev.xclient.window = icon;
    ev.xclient.message_type = ewmh.XEMBED;
    ev.xclient.format = 32;
    ev.xclient.data.l[0] = CurrentTime;
    ev.xclient.data.l[1] = message;
    ev.xclient.data.l[2] = detail;
    ev.xclient.data.l[3] = data1;
    ev.xclient.data.l[4] = data2;

    XSendEvent(dwn->display, icon, False, NoEventMask, &ev);
}

static bool xembed_get_info(Window icon, unsigned long *version, unsigned long *flags)
{
    if (dwn == NULL || dwn->display == NULL || icon == None) {
        return false;
    }

    Atom actual_type;
    int actual_format;
    unsigned long nitems, bytes_after;
    unsigned char *data = NULL;

    if (XGetWindowProperty(dwn->display, icon, ewmh.XEMBED_INFO,
                           0, 2, False, ewmh.XEMBED_INFO,
                           &actual_type, &actual_format, &nitems, &bytes_after,
                           &data) != Success || data == NULL) {
        return false;
    }

    if (nitems >= 2 && actual_format == 32) {
        unsigned long *info = (unsigned long *)data;
        if (version != NULL) *version = info[0];
        if (flags != NULL) *flags = info[1];
        XFree(data);
        return true;
    }

    XFree(data);
    return false;
}

bool xembed_dock_icon(Window icon_win)
{
    if (dwn == NULL || dwn->display == NULL || dwn->top_panel == NULL) {
        return false;
    }

    if (icon_win == None) {
        return false;
    }

    if (tray_icon_count >= MAX_TRAY_ICONS) {
        LOG_WARN("Maximum tray icons reached");
        return false;
    }

    for (int i = 0; i < tray_icon_count; i++) {
        if (tray_icons[i].window == icon_win) {
            return true;
        }
    }

    XWindowAttributes wa;
    if (!XGetWindowAttributes(dwn->display, icon_win, &wa)) {
        LOG_WARN("Failed to get tray icon attributes");
        return false;
    }

    Display *dpy = dwn->display;

    XSelectInput(dpy, icon_win, StructureNotifyMask | PropertyChangeMask);

    XReparentWindow(dpy, icon_win, dwn->top_panel->window, 0, 0);

    XResizeWindow(dpy, icon_win, TRAY_ICON_SIZE, TRAY_ICON_SIZE);

    unsigned long version = 0, flags = 0;
    bool has_info = xembed_get_info(icon_win, &version, &flags);

    TrayIcon *icon = &tray_icons[tray_icon_count];
    icon->window = icon_win;
    icon->width = TRAY_ICON_SIZE;
    icon->height = TRAY_ICON_SIZE;
    icon->mapped = !has_info || (flags & XEMBED_MAPPED);
    icon->x = 0;
    tray_icon_count++;

    xembed_send_message(icon_win, XEMBED_EMBEDDED_NOTIFY, 0,
                        dwn->top_panel->window, XEMBED_PROTOCOL_VERSION);

    if (icon->mapped) {
        XMapWindow(dpy, icon_win);
    }

    XFlush(dpy);

    LOG_INFO("Docked tray icon: window=%lu, mapped=%d", icon_win, icon->mapped);

    panel_render_all();

    return true;
}

void xembed_remove_icon(Window icon_win)
{
    if (icon_win == None) {
        return;
    }

    int found = -1;
    for (int i = 0; i < tray_icon_count; i++) {
        if (tray_icons[i].window == icon_win) {
            found = i;
            break;
        }
    }

    if (found < 0) {
        return;
    }

    LOG_DEBUG("Removing tray icon: window=%lu", icon_win);

    for (int i = found; i < tray_icon_count - 1; i++) {
        tray_icons[i] = tray_icons[i + 1];
    }
    tray_icon_count--;

    memset(&tray_icons[tray_icon_count], 0, sizeof(TrayIcon));

    panel_render_all();
}

TrayIcon *xembed_find_icon(Window icon_win)
{
    if (icon_win == None) {
        return NULL;
    }

    for (int i = 0; i < tray_icon_count; i++) {
        if (tray_icons[i].window == icon_win) {
            return &tray_icons[i];
        }
    }

    return NULL;
}

int xembed_get_icons_width(void)
{
    if (tray_icon_count == 0) {
        return 0;
    }

    return tray_icon_count * (TRAY_ICON_SIZE + TRAY_ICON_SPACING);
}

void xembed_render_icons(Panel *panel, int x)
{
    if (panel == NULL || dwn == NULL || dwn->display == NULL) {
        return;
    }

    Display *dpy = dwn->display;
    int current_x = x;
    int y = (panel->height - TRAY_ICON_SIZE) / 2;

    xembed_icons_x = x;

    for (int i = 0; i < tray_icon_count; i++) {
        TrayIcon *icon = &tray_icons[i];
        if (icon->window == None) {
            continue;
        }

        icon->x = current_x;

        if (icon->mapped) {
            XMoveResizeWindow(dpy, icon->window, current_x, y,
                              TRAY_ICON_SIZE, TRAY_ICON_SIZE);
        }

        current_x += TRAY_ICON_SIZE + TRAY_ICON_SPACING;
    }
}

int xembed_hit_test(int x)
{
    if (tray_icon_count == 0) {
        return -1;
    }

    for (int i = 0; i < tray_icon_count; i++) {
        TrayIcon *icon = &tray_icons[i];
        if (x >= icon->x && x < icon->x + TRAY_ICON_SIZE) {
            return i;
        }
    }

    return -1;
}

void xembed_handle_click(int x, int y, int button)
{
    if (dwn == NULL || dwn->display == NULL) {
        return;
    }

    int index = xembed_hit_test(x);
    if (index < 0 || index >= tray_icon_count) {
        return;
    }

    TrayIcon *icon = &tray_icons[index];
    if (icon->window == None || !icon->mapped) {
        return;
    }

    int icon_x = x - icon->x;
    int icon_y = y;

    XEvent ev;
    memset(&ev, 0, sizeof(ev));
    ev.xbutton.type = ButtonPress;
    ev.xbutton.window = icon->window;
    ev.xbutton.root = dwn->root;
    ev.xbutton.subwindow = None;
    ev.xbutton.time = CurrentTime;
    ev.xbutton.x = icon_x;
    ev.xbutton.y = icon_y;
    ev.xbutton.x_root = x;
    ev.xbutton.y_root = y;
    ev.xbutton.state = 0;
    ev.xbutton.button = button;
    ev.xbutton.same_screen = True;

    XSendEvent(dwn->display, icon->window, False, ButtonPressMask, &ev);

    ev.xbutton.type = ButtonRelease;
    XSendEvent(dwn->display, icon->window, False, ButtonReleaseMask, &ev);

    XFlush(dwn->display);
}

void xembed_update_icon_state(Window icon_win)
{
    TrayIcon *icon = xembed_find_icon(icon_win);
    if (icon == NULL) {
        return;
    }

    unsigned long version = 0, flags = 0;
    if (!xembed_get_info(icon_win, &version, &flags)) {
        return;
    }

    bool should_map = (flags & XEMBED_MAPPED) != 0;
    if (should_map != icon->mapped) {
        icon->mapped = should_map;
        if (should_map) {
            XMapWindow(dwn->display, icon_win);
        } else {
            XUnmapWindow(dwn->display, icon_win);
        }
        panel_render_all();
    }
}

/*============================================================================*/
/* Fade Control Implementation                                               */
/*============================================================================*/

/* Callback for speed slider value changes */
static void fade_speed_changed_callback(GenericSlider *slider, int value)
{
    (void)slider;
    
    /* Get old value before changing */
    float old_speed = config_get_fade_speed();
    
    /* Map 0-100 to 0.1-3.0 */
    float speed = 0.1f + (value / 100.0f) * 2.9f;
    LOG_DEBUG("Fade speed changed: slider_value=%d -> speed=%.2f", value, speed);
    config_set_fade_speed(speed);
    
    /* Update ambient speed in real-time */
    if (dwn != NULL) {
        dwn->ambient_speed = speed;
    }
    
    /* Emit WebSocket event */
    api_emit_fade_speed_changed(old_speed, speed);
    
    panel_render_all();
}

/* Callback for intensity slider value changes */
static void fade_intensity_changed_callback(GenericSlider *slider, int value)
{
    (void)slider;
    
    /* Get old value before changing */
    float old_intensity = config_get_fade_intensity();
    
    /* Map 0-100 to 0.0-1.0 */
    float intensity = value / 100.0f;
    LOG_DEBUG("Fade intensity changed: slider_value=%d -> intensity=%.2f", value, intensity);
    config_set_fade_intensity(intensity);
    
    /* Emit WebSocket event */
    api_emit_fade_intensity_changed(old_intensity, intensity);
    
    panel_render_all();
}

void fade_controls_init(void)
{
    if (dwn == NULL || dwn->display == NULL) {
        return;
    }
    
    /* Create speed slider */
    fade_speed_control.slider = slider_create(0, 0, "Spd:", true,
                                               fade_speed_changed_callback, NULL);
    if (fade_speed_control.slider != NULL) {
        /* Set initial value from config (map 0.1-3.0 to 0-100) */
        float speed = config_get_fade_speed();
        int slider_value = (int)((speed - 0.1f) / 2.9f * 100);
        LOG_DEBUG("Fade controls init: speed=%.2f -> slider_value=%d", speed, slider_value);
        slider_set_value(fade_speed_control.slider, slider_value);
    }
    
    /* Create intensity slider */
    fade_intensity_control.slider = slider_create(0, 0, "Int:", true,
                                                   fade_intensity_changed_callback, NULL);
    if (fade_intensity_control.slider != NULL) {
        /* Set initial value from config (map 0.0-1.0 to 0-100) */
        float intensity = config_get_fade_intensity();
        LOG_DEBUG("Fade controls init: intensity=%.2f -> slider_value=%d", intensity, (int)(intensity * 100));
        slider_set_value(fade_intensity_control.slider, (int)(intensity * 100));
    }
}

void fade_controls_cleanup(void)
{
    if (fade_speed_control.slider != NULL) {
        slider_destroy(fade_speed_control.slider);
        fade_speed_control.slider = NULL;
    }
    if (fade_intensity_control.slider != NULL) {
        slider_destroy(fade_intensity_control.slider);
        fade_intensity_control.slider = NULL;
    }
}

void fade_controls_hide_all(void)
{
    if (fade_speed_control.slider != NULL && slider_is_visible(fade_speed_control.slider)) {
        slider_hide(fade_speed_control.slider);
    }
    if (fade_intensity_control.slider != NULL && slider_is_visible(fade_intensity_control.slider)) {
        slider_hide(fade_intensity_control.slider);
    }
}

void fade_controls_render(Panel *panel, int x)
{
    if (panel == NULL || dwn == NULL) {
        return;
    }
    
    const ColorScheme *colors = config_get_colors();
    
    /* Calculate text_y same as systray_render_widgets */
    int font_height = 12;
    if (dwn->xft_font != NULL) {
        font_height = dwn->xft_font->ascent;
    } else if (dwn->font != NULL) {
        font_height = dwn->font->ascent;
    }
    int text_y = (panel->height + font_height) / 2;
    
    int current_x = x;
    
    /* Render fade speed icon/button */
    fade_speed_icon_x = current_x;
    float speed = config_get_fade_speed();
    char speed_label[16];
    snprintf(speed_label, sizeof(speed_label), "S:%.1f", speed);
    
    unsigned long speed_color = ambient_glow_bg(colors->panel_fg, 
                                                 dwn->ambient_phase + PHASE_OFFSET_SYSTRAY);
    draw_utf8_text(panel->buffer, current_x, text_y, speed_label, speed_color);
    current_x += get_text_width(speed_label) + SYSTRAY_SPACING;
    
    /* Render fade intensity icon/button */
    fade_intensity_icon_x = current_x;
    float intensity = config_get_fade_intensity();
    char intensity_label[16];
    snprintf(intensity_label, sizeof(intensity_label), "I:%d%%", (int)(intensity * 100));
    
    unsigned long intensity_color = ambient_glow_bg(colors->panel_fg,
                                                     dwn->ambient_phase + PHASE_OFFSET_SYSTRAY);
    draw_utf8_text(panel->buffer, current_x, text_y, intensity_label, intensity_color);
}

int fade_controls_get_width(void)
{
    /* Calculate approximate width for both controls */
    return 80; /* Approximate width for "S:0.5 I:100%" + spacing */
}

int fade_controls_hit_test(int x)
{
    /* Check if x position hits any fade control icon */
    /* Return 1 for speed, 2 for intensity, 0 for none */
    
    if (x >= fade_speed_icon_x && x < fade_speed_icon_x + 40) {
        return 1;  /* Speed control */
    }
    if (x >= fade_intensity_icon_x && x < fade_intensity_icon_x + 50) {
        return 2;  /* Intensity control */
    }
    return 0;
}

void fade_controls_handle_click(int control_id, int x, int y, int button)
{
    (void)x;
    (void)y;
    
    if (control_id == 1) {
        /* Speed control clicked */
        if (button == 1) {
            /* Toggle speed slider */
            if (fade_speed_control.slider == NULL) {
                int panel_height = config_get_panel_height();
                fade_speed_control.slider = slider_create(fade_speed_icon_x, panel_height,
                                                           "Spd:", true,
                                                           fade_speed_changed_callback, NULL);
                if (fade_speed_control.slider != NULL) {
                    float speed = config_get_fade_speed();
                    int slider_value = (int)((speed - 0.1f) / 2.9f * 100);
                    slider_set_value(fade_speed_control.slider, slider_value);
                }
            }
            
            if (fade_speed_control.slider != NULL) {
                if (slider_is_visible(fade_speed_control.slider)) {
                    slider_hide(fade_speed_control.slider);
                } else {
                    /* Hide other sliders */
                    if (volume_slider != NULL && volume_slider->visible) {
                        volume_slider_hide(volume_slider);
                    }
                    if (fade_intensity_control.slider != NULL && 
                        slider_is_visible(fade_intensity_control.slider)) {
                        slider_hide(fade_intensity_control.slider);
                    }
                    
                    slider_show(fade_speed_control.slider, fade_speed_icon_x, 
                               config_get_panel_height());
                }
            }
        } else if (button == 4) {
            /* Scroll up - increase speed */
            float old_speed = config_get_fade_speed();
            float speed = old_speed + 0.1f;
            if (speed > 3.0f) speed = 3.0f;
            config_set_fade_speed(speed);
            if (dwn != NULL) {
                dwn->ambient_speed = speed;
            }
            if (fade_speed_control.slider != NULL) {
                int slider_value = (int)((speed - 0.1f) / 2.9f * 100);
                slider_set_value(fade_speed_control.slider, slider_value);
            }
            api_emit_fade_speed_changed(old_speed, speed);
            panel_render_all();
        } else if (button == 5) {
            /* Scroll down - decrease speed */
            float old_speed = config_get_fade_speed();
            float speed = old_speed - 0.1f;
            if (speed < 0.1f) speed = 0.1f;
            config_set_fade_speed(speed);
            if (dwn != NULL) {
                dwn->ambient_speed = speed;
            }
            if (fade_speed_control.slider != NULL) {
                int slider_value = (int)((speed - 0.1f) / 2.9f * 100);
                slider_set_value(fade_speed_control.slider, slider_value);
            }
            api_emit_fade_speed_changed(old_speed, speed);
            panel_render_all();
        }
    } else if (control_id == 2) {
        /* Intensity control clicked */
        if (button == 1) {
            /* Toggle intensity slider */
            if (fade_intensity_control.slider == NULL) {
                int panel_height = config_get_panel_height();
                fade_intensity_control.slider = slider_create(fade_intensity_icon_x, panel_height,
                                                               "Int:", true,
                                                               fade_intensity_changed_callback, NULL);
                if (fade_intensity_control.slider != NULL) {
                    float intensity = config_get_fade_intensity();
                    slider_set_value(fade_intensity_control.slider, (int)(intensity * 100));
                }
            }
            
            if (fade_intensity_control.slider != NULL) {
                if (slider_is_visible(fade_intensity_control.slider)) {
                    slider_hide(fade_intensity_control.slider);
                } else {
                    /* Hide other sliders */
                    if (volume_slider != NULL && volume_slider->visible) {
                        volume_slider_hide(volume_slider);
                    }
                    if (fade_speed_control.slider != NULL &&
                        slider_is_visible(fade_speed_control.slider)) {
                        slider_hide(fade_speed_control.slider);
                    }
                    
                    slider_show(fade_intensity_control.slider, fade_intensity_icon_x,
                               config_get_panel_height());
                }
            }
        } else if (button == 4) {
            /* Scroll up - increase intensity */
            float old_intensity = config_get_fade_intensity();
            float intensity = old_intensity + 0.05f;
            if (intensity > 1.0f) intensity = 1.0f;
            config_set_fade_intensity(intensity);
            if (fade_intensity_control.slider != NULL) {
                slider_set_value(fade_intensity_control.slider, (int)(intensity * 100));
            }
            api_emit_fade_intensity_changed(old_intensity, intensity);
            panel_render_all();
        } else if (button == 5) {
            /* Scroll down - decrease intensity */
            float old_intensity = config_get_fade_intensity();
            float intensity = old_intensity - 0.05f;
            if (intensity < 0.0f) intensity = 0.0f;
            config_set_fade_intensity(intensity);
            if (fade_intensity_control.slider != NULL) {
                slider_set_value(fade_intensity_control.slider, (int)(intensity * 100));
            }
            api_emit_fade_intensity_changed(old_intensity, intensity);
            panel_render_all();
        }
    }
}
