/*
    * main.cpp
    * MontaukOS Desktop Environment - initialization, external window polling, run loop
    * Copyright (c) 2026 Daniel Hammer
*/

#include "desktop_internal.hpp"
#include <montauk/config.h>
#include <montauk/user.h>
#include <gui/clipboard.hpp>
#include <gui/dialogs.hpp>

// ============================================================================
// App Manifest Scanning
// ============================================================================

// Extract the basename from a readdir entry.
// readdir returns full paths from drive root (e.g. "apps/doom/") —
// strip the directory prefix and any trailing slash to get just "doom".
static void extract_basename(char* out, int outSz, const char* entry) {
    // Strip trailing slash
    int len = montauk::slen(entry);
    while (len > 0 && entry[len - 1] == '/') len--;

    // Find last slash before that
    int last_slash = -1;
    for (int i = 0; i < len; i++) {
        if (entry[i] == '/') last_slash = i;
    }

    const char* base = (last_slash >= 0) ? entry + last_slash + 1 : entry;
    int base_len = len - (last_slash >= 0 ? last_slash + 1 : 0);
    if (base_len >= outSz) base_len = outSz - 1;
    montauk::memcpy(out, base, base_len);
    out[base_len] = '\0';
}

void desktop_scan_apps(DesktopState* ds) {
    ds->external_app_count = 0;

    // Ensure the apps directory exists
    montauk::fmkdir("0:/apps");

    const char* entries[32];
    int count = montauk::readdir("0:/apps", entries, 32);

    for (int i = 0; i < count && ds->external_app_count < MAX_EXTERNAL_APPS; i++) {
        // readdir returns paths like "apps/doom/" — extract just "doom"
        char dirname[64];
        extract_basename(dirname, sizeof(dirname), entries[i]);
        if (dirname[0] == '\0') continue;

        // Try to open the manifest in this app directory
        char manifest_path[128];
        snprintf(manifest_path, sizeof(manifest_path), "0:/apps/%s/manifest.toml", dirname);

        int fh = montauk::open(manifest_path);
        if (fh < 0) continue;

        uint64_t sz = montauk::getsize(fh);
        if (sz == 0 || sz > 4096) { montauk::close(fh); continue; }

        char* text = (char*)montauk::malloc(sz + 1);
        if (!text) { montauk::close(fh); continue; }
        int bytes_read = montauk::read(fh, (uint8_t*)text, 0, sz);
        montauk::close(fh);
        if (bytes_read != (int)sz) {
            montauk::mfree(text);
            continue;
        }
        text[sz] = '\0';

        auto doc = montauk::toml::parse(text);
        montauk::mfree(text);

        ExternalApp* app = &ds->external_apps[ds->external_app_count];

        // Read manifest fields
        const char* name = doc.get_string("app.name", "Unknown");
        const char* binary = doc.get_string("app.binary", "");
        const char* icon_file = doc.get_string("app.icon", "");
        const char* category = doc.get_string("menu.category", "Applications");
        bool visible = doc.get_bool("menu.visible", true);
        bool launch_with_home = doc.get_bool("launch.pass_home_dir", true);
        if (binary[0] == '\0') {
            doc.destroy();
            continue;
        }

        montauk::strncpy(app->name, name, sizeof(app->name));
        montauk::strncpy(app->category, category, sizeof(app->category));
        app->menu_visible = visible;
        app->launch_with_home = launch_with_home;

        // Build full binary path: 0:/apps/<dir>/<binary>
        snprintf(app->binary_path, sizeof(app->binary_path),
                 "0:/apps/%s/%s", dirname, binary);

        // Load icon from app directory
        app->icon = {};
        app->icon_path[0] = '\0';
        if (icon_file[0]) {
            snprintf(app->icon_path, sizeof(app->icon_path),
                     "0:/apps/%s/%s", dirname, icon_file);
            Color defColor = colors::ICON_COLOR;
            app->icon = svg_load(app->icon_path, 20, 20, defColor);
        }

        doc.destroy();
        ds->external_app_count++;
    }
}

// ============================================================================
// Menu Builder
// ============================================================================

// Category definitions and their ordering
static const char* CATEGORY_NAMES[] = { "Applications", "Internet", "System", "Games" };
static constexpr int NUM_CATEGORIES = 4;

void desktop_build_menu(DesktopState* ds) {
    menu_row_count = 0;
    int builtin_count = 0;
    const DesktopBuiltinEntry* builtins = desktop_builtin_registry(&builtin_count);

    // Build each category
    for (int cat = 0; cat < NUM_CATEGORIES; cat++) {
        menu_add_category(CATEGORY_NAMES[cat]);

        // Add desktop builtins in this category
        for (int b = 0; b < builtin_count; b++) {
            if (builtins[b].launcher_category == cat) {
                menu_add_embedded(builtins[b].label, builtins[b].id,
                                  desktop_builtin_menu_icon(ds, builtins[b].id));
            }
        }

        // Add external apps in this category
        for (int x = 0; x < ds->external_app_count; x++) {
            ExternalApp* app = &ds->external_apps[x];
            if (!app->menu_visible) continue;
            if (montauk::streq(app->category, CATEGORY_NAMES[cat])) {
                menu_add_external(app->name, app->binary_path, &app->icon, app->launch_with_home);
            }
        }
    }

    // Divider + always-visible entries
    menu_add_category("");  // divider (cat 4, always expanded)
    for (int b = 0; b < builtin_count; b++) {
        if (builtins[b].launcher_footer) {
            menu_add_embedded(builtins[b].label, builtins[b].id,
                              desktop_builtin_menu_icon(ds, builtins[b].id));
        }
    }
}

// ============================================================================
// Initialization
// ============================================================================

void gui::desktop_init(DesktopState* ds) {
    ds->screen_w = ds->fb.width();
    ds->screen_h = ds->fb.height();
    ds->background_cache = nullptr;
    ds->background_cache_pitch = ds->fb.pitch();
    ds->background_cache_dirty = true;

    // Immediately clear the screen to hide flanterm boot text
    ds->fb.clear(colors::DESKTOP_BG);
    ds->fb.flip();

    // Load TrueType fonts
    fonts::init();

    ds->window_count = 0;
    ds->focused_window = -1;
    ds->prev_buttons = 0;
    ds->app_menu_open = false;
    desktop_init_launcher(ds);

    montauk::memset(&ds->mouse, 0, sizeof(Montauk::MouseState));
    montauk::set_mouse_bounds(ds->screen_w - 1, ds->screen_h - 1);
    gui::clipboard_clear();

    // Load SVG icons — scalable (colorful) for app menu, symbolic for toolbar/panel
    Color defColor = colors::ICON_COLOR;
    ds->icon_terminal    = svg_load("0:/icons/utilities-terminal.svg",        20, 20, defColor);
    ds->icon_filemanager = svg_load("0:/icons/system-file-manager.svg",      20, 20, defColor);
    ds->icon_sysinfo     = svg_load("0:/icons/preferences-desktop-apps.svg", 20, 20, defColor);
    ds->icon_appmenu     = svg_load("0:/icons/view-app-grid-symbolic.svg",   20, 20, colors::PANEL_TEXT);
    ds->icon_folder      = svg_load("0:/icons/folder.svg",                   16, 16, defColor);
    ds->icon_file        = svg_load("0:/icons/text-x-generic.svg",           16, 16, defColor);
    ds->icon_network     = svg_load("0:/icons/network-wired-symbolic.svg",   16, 16, colors::PANEL_TEXT);
    ds->icon_go_up       = svg_load("0:/icons/go-up-symbolic.svg",           16, 16, defColor);
    ds->icon_go_back     = svg_load("0:/icons/go-previous-symbolic.svg",     16, 16, defColor);
    ds->icon_go_forward  = svg_load("0:/icons/go-next-symbolic.svg",         16, 16, defColor);
    ds->icon_home        = svg_load("0:/icons/user-home.svg",                16, 16, defColor);
    ds->icon_exec        = svg_load("0:/icons/utilities-terminal.svg",        16, 16, defColor);

    ds->icon_folder_lg = svg_load("0:/icons/folder.svg",                   48, 48, defColor);
    ds->icon_file_lg   = svg_load("0:/icons/text-x-generic.svg",           48, 48, defColor);
    ds->icon_exec_lg   = svg_load("0:/icons/utilities-terminal.svg",        48, 48, defColor);
    // drive icons loaded lazily by file manager to reduce startup heap pressure

    ds->icon_settings = svg_load("0:/icons/preferences-desktop-apps.svg", 20, 20, defColor);
    ds->icon_reboot   = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor);
    ds->icon_shutdown = svg_load("0:/icons/system-shutdown.svg", 20, 20, defColor);
    ds->icon_sleep    = svg_load("0:/icons/sleep.svg", 20, 20, defColor);
    ds->icon_logout   = svg_load("0:/icons/gnome-logout.svg",    20, 20, defColor);

    ds->icon_volume   = svg_load("0:/icons/audio-volume-high-symbolic.svg", 16, 16, colors::PANEL_TEXT);
    ds->icon_lock     = svg_load("0:/icons/lock.svg",                    20, 20, defColor);

    // Scan 0:/apps/ for external app manifests and build the menu
    desktop_scan_apps(ds);
    desktop_build_menu(ds);

    // Settings defaults
    ds->settings.bg_gradient = true;
    ds->settings.bg_image = false;
    ds->settings.bg_grad_top = Color::from_rgb(0xD0, 0xD8, 0xE8);
    ds->settings.bg_grad_bottom = Color::from_rgb(0xA0, 0xA8, 0xB8);
    ds->settings.bg_solid = Color::from_rgb(0xD0, 0xD8, 0xE8);
    ds->settings.bg_image_path[0] = '\0';
    ds->settings.bg_wallpaper = nullptr;
    ds->settings.bg_wallpaper_w = 0;
    ds->settings.bg_wallpaper_h = 0;
    ds->settings.panel_color = Color::from_rgb(0x10, 0x10, 0x10);
    ds->settings.accent_color = colors::ACCENT;
    ds->settings.show_shadows = true;
    ds->settings.clock_24h = true;
    ds->settings.ui_scale = 1;
    ds->settings.tz_offset_minutes = 60; // default CET (UTC+1)

    // Load per-user desktop settings
    {
        auto doc = montauk::config::load_user(ds->current_user, "desktop");

        const char* wp = doc.get_string("wallpaper.path", "");
        if (wp[0] != '\0') {
            wallpaper_load(&ds->settings, wp, ds->screen_w, ds->screen_h);
        } else {
            // Fall back to system-wide default wallpaper from 0:/config/desktop.toml
            auto sys = montauk::config::load("desktop");
            const char* def_wp = sys.get_string("wallpaper.path", "");
            if (def_wp[0] != '\0') {
                wallpaper_load(&ds->settings, def_wp, ds->screen_w, ds->screen_h);
            }
            sys.destroy();
        }

        // Restore other settings from user config
        const char* bg_mode = doc.get_string("background.mode", "");
        if (montauk::streq(bg_mode, "solid")) {
            ds->settings.bg_gradient = false;
            ds->settings.bg_image = false;
        } else if (montauk::streq(bg_mode, "gradient")) {
            ds->settings.bg_gradient = true;
            ds->settings.bg_image = false;
        }
        // (image mode is set by wallpaper_load above)

        // Background colors
        int64_t solid = doc.get_int("background.solid_color", -1);
        if (solid >= 0) ds->settings.bg_solid = Color::from_rgb(
            (uint8_t)((solid >> 16) & 0xFF), (uint8_t)((solid >> 8) & 0xFF), (uint8_t)(solid & 0xFF));
        int64_t gtop = doc.get_int("background.grad_top", -1);
        if (gtop >= 0) ds->settings.bg_grad_top = Color::from_rgb(
            (uint8_t)((gtop >> 16) & 0xFF), (uint8_t)((gtop >> 8) & 0xFF), (uint8_t)(gtop & 0xFF));
        int64_t gbot = doc.get_int("background.grad_bottom", -1);
        if (gbot >= 0) ds->settings.bg_grad_bottom = Color::from_rgb(
            (uint8_t)((gbot >> 16) & 0xFF), (uint8_t)((gbot >> 8) & 0xFF), (uint8_t)(gbot & 0xFF));

        // Appearance colors
        int64_t panel = doc.get_int("appearance.panel_color", -1);
        if (panel >= 0) ds->settings.panel_color = Color::from_rgb(
            (uint8_t)((panel >> 16) & 0xFF), (uint8_t)((panel >> 8) & 0xFF), (uint8_t)(panel & 0xFF));
        int64_t accent = doc.get_int("appearance.accent_color", -1);
        if (accent >= 0) ds->settings.accent_color = Color::from_rgb(
            (uint8_t)((accent >> 16) & 0xFF), (uint8_t)((accent >> 8) & 0xFF), (uint8_t)(accent & 0xFF));

        int64_t scale = doc.get_int("display.ui_scale", 1);
        ds->settings.ui_scale = (int)scale;
        ds->settings.clock_24h = doc.get_bool("display.clock_24h", true);
        ds->settings.show_shadows = doc.get_bool("display.show_shadows", true);

        doc.destroy();
    }

    // Load timezone config
    {
        auto tz = montauk::config::load("timezone");
        ds->settings.tz_offset_minutes = (int)tz.get_int("timezone.offset_minutes", 60);
        tz.destroy();
    }
    montauk::settz(ds->settings.tz_offset_minutes);

    montauk::win_setscale(ds->settings.ui_scale);

    ds->ctx_menu_open = false;
    ds->ctx_menu_x = 0;
    ds->ctx_menu_y = 0;

    ds->net_popup_open = false;
    montauk::get_netcfg(&ds->cached_net_cfg);
    ds->net_cfg_last_poll = montauk::get_milliseconds();
    ds->net_icon_rect = {0, 0, 0, 0};

    ds->vol_popup_open = false;
    ds->vol_icon_rect = {0, 0, 0, 0};
    int vol = montauk::audio_get_volume(0);
    ds->vol_level = vol >= 0 ? vol : 80;
    ds->vol_muted = false;
    ds->vol_pre_mute = ds->vol_level;
    ds->vol_dragging = false;
    ds->vol_last_poll = montauk::get_milliseconds();

    ds->closing_ext_count = 0;

    ds->screen_locked = false;
    ds->lock_password[0] = '\0';
    ds->lock_password_len = 0;
    ds->lock_error[0] = '\0';
    ds->lock_show_error = false;
}

// ============================================================================
// External Window Polling
// ============================================================================

static uint64_t desktop_clock_token() {
    Montauk::DateTime dt;
    montauk::gettime(&dt);
    return ((uint64_t)dt.Year << 32)
         | ((uint64_t)dt.Month << 24)
         | ((uint64_t)dt.Day << 16)
         | ((uint64_t)dt.Hour << 8)
         | (uint64_t)dt.Minute;
}

static bool desktop_has_visible_dirty_window(const DesktopState* ds) {
    for (int i = 0; i < ds->window_count; i++) {
        const Window& win = ds->windows[i];
        if (win.state == WIN_CLOSED || win.state == WIN_MINIMIZED) continue;
        if (win.dirty) return true;
    }
    return false;
}

static bool desktop_has_active_interaction(const DesktopState* ds) {
    if (ds->vol_dragging) return true;

    for (int i = 0; i < ds->window_count; i++) {
        const Window& win = ds->windows[i];
        if (win.state == WIN_CLOSED || win.state == WIN_MINIMIZED) continue;
        if (win.dragging || win.resizing) return true;
    }

    return false;
}

static void desktop_clear_window_dirty(DesktopState* ds) {
    for (int i = 0; i < ds->window_count; i++) {
        ds->windows[i].dirty = false;
    }
}

static bool desktop_netcfg_equal(const Montauk::NetCfg& a, const Montauk::NetCfg& b) {
    if (a.ipAddress != b.ipAddress || a.subnetMask != b.subnetMask ||
        a.gateway != b.gateway || a.dnsServer != b.dnsServer) {
        return false;
    }
    for (int i = 0; i < 6; i++) {
        if (a.macAddress[i] != b.macAddress[i]) return false;
    }
    return true;
}

static bool desktop_refresh_panel_state(DesktopState* ds, uint64_t now) {
    if (ds->screen_locked) return false;

    bool changed = false;
    if (now - ds->net_cfg_last_poll > 5000) {
        Montauk::NetCfg next;
        montauk::get_netcfg(&next);
        if (!desktop_netcfg_equal(next, ds->cached_net_cfg)) {
            ds->cached_net_cfg = next;
            changed = true;
        }
        ds->net_cfg_last_poll = now;
    }

    if (now - ds->vol_last_poll > 5000) {
        int v = montauk::audio_get_volume(0);
        if (v >= 0 && !ds->vol_muted && v != ds->vol_level) {
            ds->vol_level = v;
            changed = true;
        }
        ds->vol_last_poll = now;
    }

    return changed;
}

bool desktop_poll_external_windows(DesktopState* ds) {
    bool changed = false;
    Montauk::WinInfo extWins[8];
    int extCount = montauk::win_enumerate(extWins, 8);

    // Check for new external windows and map them
    for (int e = 0; e < extCount; e++) {
        int extId = extWins[e].id;

        // Check if we already have this window
        bool found = false;
        for (int i = 0; i < ds->window_count; i++) {
            if (ds->windows[i].external && ds->windows[i].ext_win_id == extId) {
                found = true;
                bool remapRequested = false;
                uint64_t currentVa = (uint64_t)(uintptr_t)ds->windows[i].content;
                if (ds->windows[i].content_w != extWins[e].width ||
                    ds->windows[i].content_h != extWins[e].height) {
                    ds->windows[i].content_w = extWins[e].width;
                    ds->windows[i].content_h = extWins[e].height;
                    ds->windows[i].dirty = true;
                    remapRequested = true;
                    if (ds->windows[i].state != WIN_MINIMIZED && ds->windows[i].state != WIN_CLOSED) {
                        changed = true;
                    }
                }
                if (!montauk::streq(ds->windows[i].title, extWins[e].title)) {
                    montauk::strncpy(ds->windows[i].title, extWins[e].title, MAX_TITLE_LEN);
                    if (ds->windows[i].state != WIN_MINIMIZED && ds->windows[i].state != WIN_CLOSED) {
                        changed = true;
                    }
                }
                // Update dirty flag and cursor
                if (extWins[e].dirty) {
                    ds->windows[i].dirty = true;
                    if (ds->windows[i].state != WIN_MINIMIZED && ds->windows[i].state != WIN_CLOSED) {
                        changed = true;
                    }
                }
                if (ds->windows[i].ext_cursor != extWins[e].cursor) {
                    changed = true;
                }
                ds->windows[i].ext_cursor = extWins[e].cursor;

                if (remapRequested && currentVa != 0) {
                    montauk::win_unmap(extId);
                    ds->windows[i].content = nullptr;
                    currentVa = 0;
                }

                uint64_t va = currentVa;
                if (remapRequested || currentVa == 0) {
                    va = montauk::win_map(extId);
                }

                if (va != 0 && va != currentVa) {
                    ds->windows[i].content = (uint32_t*)va;
                    ds->windows[i].content_w = extWins[e].width;
                    ds->windows[i].content_h = extWins[e].height;
                    ds->windows[i].dirty = true;
                    changed = true;
                    // Clear from closing list if the slot was reused
                    for (int c = 0; c < ds->closing_ext_count; c++) {
                        if (ds->closing_ext_ids[c] == extId) {
                            ds->closing_ext_ids[c] = ds->closing_ext_ids[--ds->closing_ext_count];
                            break;
                        }
                    }
                }
                break;
            }
        }

        if (!found && ds->window_count < MAX_WINDOWS) {
            // Skip windows we've already sent a close event to — the owning
            // process hasn't destroyed them yet, so they still appear in
            // win_enumerate.  Re-creating them would place them at the
            // default position instead of where the user dragged them.
            bool closing = false;
            for (int c = 0; c < ds->closing_ext_count; c++) {
                if (ds->closing_ext_ids[c] == extId) { closing = true; break; }
            }
            if (closing) continue;

            // Map the pixel buffer into our address space
            uint64_t va = montauk::win_map(extId);
            if (va == 0) continue;

            int idx = ds->window_count;
            Window* win = &ds->windows[idx];
            montauk::memset(win, 0, sizeof(Window));

            montauk::strncpy(win->title, extWins[e].title, MAX_TITLE_LEN);
            int w = extWins[e].width;
            int h = extWins[e].height;
            // Position the window centered-ish
            int wx = (ds->screen_w - w - 2 * BORDER_WIDTH) / 2 + idx * 30;
            int wy = PANEL_HEIGHT + 20 + idx * 30;
            win->frame = {wx, wy, w + 2 * BORDER_WIDTH, h + TITLEBAR_HEIGHT + BORDER_WIDTH};
            win->state = WIN_NORMAL;
            win->z_order = idx;
            win->focused = true;
            win->dirty = true;
            win->dragging = false;
            win->resizing = false;
            win->saved_frame = win->frame;

            // Point content to the shared pixel buffer
            win->content = (uint32_t*)va;
            win->content_w = w;
            win->content_h = h;

            win->on_draw = nullptr;
            win->on_mouse = nullptr;
            win->on_key = nullptr;
            win->on_close = nullptr;
            win->on_poll = nullptr;
            win->app_data = nullptr;
            win->external = true;
            win->ext_win_id = extId;

            // Unfocus previous window
            if (ds->focused_window >= 0 && ds->focused_window < ds->window_count) {
                ds->windows[ds->focused_window].focused = false;
            }
            ds->focused_window = idx;
            ds->window_count++;
            changed = true;
        }
    }

    // Check for removed external windows (process exited)
    for (int i = ds->window_count - 1; i >= 0; i--) {
        if (!ds->windows[i].external) continue;

        int extId = ds->windows[i].ext_win_id;
        bool stillExists = false;
        for (int e = 0; e < extCount; e++) {
            if (extWins[e].id == extId) {
                stillExists = true;
                break;
            }
        }

        if (!stillExists) {
            // Window gone — remove without freeing content (shared memory)
            if (ds->windows[i].content != nullptr) {
                montauk::win_unmap(extId);
            }
            ds->windows[i].content = nullptr; // prevent free
            gui::desktop_close_window(ds, i);
            changed = true;
        }
    }

    // Purge closing IDs that the kernel no longer reports (fully destroyed)
    for (int c = ds->closing_ext_count - 1; c >= 0; c--) {
        bool alive = false;
        for (int e = 0; e < extCount; e++) {
            if (extWins[e].id == ds->closing_ext_ids[c]) { alive = true; break; }
        }
        if (!alive) {
            ds->closing_ext_ids[c] = ds->closing_ext_ids[--ds->closing_ext_count];
        }
    }

    return changed;
}

// ============================================================================
// Run Loop
// ============================================================================

void gui::desktop_run(DesktopState* ds) {
    uint64_t lastClockToken = 0;
    uint64_t nextClockPollMs = 0;
    uint64_t lastLauncherBlinkToken = ~0ull;
    uint64_t inputSerial = montauk::input_wait(0, 0);
    bool firstFrame = true;

    for (;;) {
        bool mouseChanged = false;
        bool keyboardChanged = false;
        bool sceneChanged = false;

        // Poll mouse state
        int prevMouseX = ds->mouse.x;
        int prevMouseY = ds->mouse.y;
        uint8_t prevMouseButtons = ds->mouse.buttons;
        ds->prev_buttons = ds->mouse.buttons;
        montauk::mouse_state(&ds->mouse);
        mouseChanged = ds->mouse.x != prevMouseX
                    || ds->mouse.y != prevMouseY
                    || ds->mouse.buttons != prevMouseButtons
                    || ds->mouse.scrollDelta != 0;
        sceneChanged |= mouseChanged;

        // Poll keyboard events
        while (montauk::is_key_available()) {
            Montauk::KeyEvent key;
            montauk::getkey(&key);
            desktop_handle_keyboard(ds, key);
            keyboardChanged = true;
        }
        sceneChanged |= keyboardChanged;

        // Poll external windows (discover new, remove dead, update dirty)
        sceneChanged |= desktop_poll_external_windows(ds);

        if (!ds->screen_locked) {
            // Poll windows that have a poll callback
            for (int i = 0; i < ds->window_count; i++) {
                Window* win = &ds->windows[i];
                if (win->state == WIN_CLOSED) continue;
                if (win->on_poll) {
                    win->on_poll(win);
                }
            }
        }

        // Handle mouse events
        if (mouseChanged) {
            desktop_handle_mouse(ds);
        }

        if (!ds->screen_locked && (mouseChanged || keyboardChanged)) {
            // Re-poll external windows so that any killed during mouse/key
            // handling are removed before we touch their pixel buffers.
            sceneChanged |= desktop_poll_external_windows(ds);
        }

        uint64_t now = montauk::get_milliseconds();
        sceneChanged |= desktop_refresh_panel_state(ds, now);

        uint64_t launcherBlinkToken = desktop_launcher_blink_token(ds, now);
        if (launcherBlinkToken != lastLauncherBlinkToken) {
            lastLauncherBlinkToken = launcherBlinkToken;
            sceneChanged = true;
        }

        if (now >= nextClockPollMs) {
            nextClockPollMs = now + 1000;
            uint64_t clockToken = desktop_clock_token();
            if (clockToken != lastClockToken) {
                lastClockToken = clockToken;
                sceneChanged = true;
            }
        }

        sceneChanged |= desktop_has_visible_dirty_window(ds);

        if (firstFrame || sceneChanged) {
            desktop_compose(ds);
            ds->fb.flip();
            desktop_clear_window_dirty(ds);
            firstFrame = false;
        }

        uint64_t waitMs = (desktop_has_active_interaction(ds) || mouseChanged || keyboardChanged)
            ? 1
            : (sceneChanged ? 4 : 16);
        inputSerial = montauk::input_wait(inputSerial, waitMs);
    }
}

// ============================================================================
// Entry Point
// ============================================================================

static DesktopState* g_desktop;

static constexpr const char kDesktopBinaryName[] = "desktop.elf";
static constexpr int DESKTOP_PROC_SCAN_MAX = 256;

static bool desktop_proc_alive(const Montauk::ProcInfo& proc) {
    return proc.state == 1 || proc.state == 2 || proc.state == 3;
}

static bool proc_name_is_desktop(const char* name) {
    int name_len = montauk::slen(name);
    int binary_len = (int)sizeof(kDesktopBinaryName) - 1;
    if (name_len < binary_len) return false;

    const char* suffix = name + name_len - binary_len;
    if (!montauk::streq(suffix, kDesktopBinaryName)) return false;
    return name_len == binary_len || suffix[-1] == '/';
}

static bool existing_desktop_running() {
    static Montauk::ProcInfo procs[DESKTOP_PROC_SCAN_MAX];
    int count = montauk::proclist(procs, DESKTOP_PROC_SCAN_MAX);
    if (count <= 0) return false;

    int self_pid = montauk::getpid();
    for (int i = 0; i < count; i++) {
        if (procs[i].pid == self_pid) continue;
        if (!desktop_proc_alive(procs[i])) continue;
        if (proc_name_is_desktop(procs[i].name)) return true;
    }
    return false;
}

extern "C" void _start() {
    if (existing_desktop_running()) {
        gui::dialogs::message_box(
            "Desktop Already Running",
            "Cannot spawn new desktop process whilst an existing desktop session is active.",
            gui::dialogs::MESSAGE_BOX_OK);
        montauk::exit(0);
    }

    DesktopState* ds = (DesktopState*)montauk::malloc(sizeof(DesktopState));
    montauk::memset(ds, 0, sizeof(DesktopState));

    // Placement-new the Framebuffer since it has a constructor
    new (&ds->fb) Framebuffer();

    // Read username from spawn args
    char username[32] = {};
    montauk::getargs(username, 32);
    if (username[0] == '\0') {
        montauk::strcpy(username, "default");
    }
    montauk::strncpy(ds->current_user, username, 31);

    // Build user paths
    montauk::user::home_dir(username, ds->home_dir, sizeof(ds->home_dir));
    montauk::user::config_dir(username, ds->user_config_dir, sizeof(ds->user_config_dir));

    // Check if user is admin
    ds->is_admin = false;
    {
        montauk::user::UserInfo users[16];
        int count = montauk::user::load_users(users, 16);
        for (int i = 0; i < count; i++) {
            if (montauk::streq(users[i].username, username)) {
                ds->is_admin = montauk::streq(users[i].role, "admin");
                break;
            }
        }
    }

    g_desktop = ds;

    desktop_init(ds);
    desktop_run(ds);

    montauk::exit(0);
}
