/*
 * main.cpp
 * MontaukOS Bluetooth Manager
 * Copyright (c) 2026 Daniel Hammer
 */

#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/heap.h>
#include <montauk/config.h>
#include <gui/mtk.hpp>
#include <gui/standalone.hpp>

extern "C" {
#include <string.h>
#include <stdio.h>
}

using namespace gui;

// ============================================================================
// Constants
// ============================================================================

static constexpr int WIN_W         = 360;
static constexpr int WIN_H         = 420;
static constexpr int ROW_H         = 56;
static constexpr int TAB_H         = 36;
static constexpr int PAD           = 16;
static constexpr int BTN_W         = 90;
static constexpr int BTN_H         = 28;
static constexpr int STATUS_H      = 28;
static constexpr int SCAN_BTN_W    = 100;
static constexpr int SCROLL_STEP   = 20;
static constexpr int FIELD_H       = 32;
static constexpr int A_PAD_TOP     = 18;

static constexpr int MAX_SCAN      = 16;
// Up to 4 active links + up to 8 stored bonds shown together in the Devices tab.
static constexpr int MAX_CONNECTED = 16;
static constexpr int FORGET_W      = 64;

static constexpr Color BG_COLOR   = Color::from_rgb(0xFF, 0xFF, 0xFF);
static constexpr Color TEXT_COLOR = Color::from_rgb(0x33, 0x33, 0x33);
static constexpr Color DIM_TEXT   = Color::from_rgb(0x66, 0x66, 0x66);
static constexpr Color ACCENT     = Color::from_rgb(0x36, 0x7B, 0xF0);
static constexpr Color ROW_HOVER  = Color::from_rgb(0xF0, 0xF4, 0xFF);
static constexpr Color BORDER     = Color::from_rgb(0xCC, 0xCC, 0xCC);
static constexpr Color WHITE      = Color::from_rgb(0xFF, 0xFF, 0xFF);
static constexpr Color GREEN      = Color::from_rgb(0x2E, 0xA0, 0x43);
static constexpr Color RED        = Color::from_rgb(0xCC, 0x33, 0x33);

// ============================================================================
// State
// ============================================================================

enum Tab {
    TAB_DEVICES = 0,
    TAB_SCAN    = 1,
    TAB_ADAPTER = 2,
    TAB_COUNT   = 3,
};

static const char* g_tab_labels[TAB_COUNT] = {
    "Devices",
    "Scan",
    "Adapter",
};

static int g_win_w = WIN_W;
static int g_win_h = WIN_H;

static Tab g_tab = TAB_DEVICES;
static int g_mouse_x = -1;
static int g_mouse_y = -1;
static int g_hover_row = -1;

// Adapter info
static montauk::abi::BtAdapterInfo g_adapter;
static bool g_adapter_ok = false;

// Connected/paired devices
static montauk::abi::BtDevInfo g_devices[MAX_CONNECTED];
static char g_device_names[MAX_CONNECTED][64];
static int g_device_count = 0;

// Scan results
static montauk::abi::BtScanResult g_scan[MAX_SCAN];
static int g_scan_count = 0;
static bool g_scanning = false;

// Adapter MAC editing
static char g_mac_value[24] = {};
static mtk::TextInputState g_mac_input = {};
static bool g_mac_focus = false;
static uint64_t g_rng = 0;

// Scroll
static int g_scroll = 0;

// Status message
static char g_status[80];
static uint64_t g_status_time = 0;

// ============================================================================
// Theme and layout
// ============================================================================

static mtk::Theme bt_theme() {
    mtk::Theme theme = mtk::make_theme();
    theme.window_bg = BG_COLOR;
    theme.surface = Color::from_rgb(0xF5, 0xF5, 0xF5);
    theme.surface_alt = Color::from_rgb(0xF8, 0xF8, 0xF8);
    theme.surface_hover = ROW_HOVER;
    theme.border = BORDER;
    theme.text = TEXT_COLOR;
    theme.text_muted = Color::from_rgb(0x88, 0x88, 0x88);
    theme.text_subtle = DIM_TEXT;
    theme.accent_soft = ROW_HOVER;
    theme.danger = RED;
    theme.danger_hover = mtk::darken(RED, 18);
    theme.disabled_bg = Color::from_rgb(0xE8, 0xE8, 0xE8);
    theme.disabled_fg = Color::from_rgb(0x9A, 0x9A, 0x9A);
    return theme;
}

static Rect tab_bar_rect() {
    return {0, 0, g_win_w, TAB_H};
}

static Rect content_rect() {
    return {0, TAB_H, g_win_w, gui_max(g_win_h - TAB_H - STATUS_H, 0)};
}

static Rect status_bar_rect() {
    return {0, g_win_h - STATUS_H, g_win_w, STATUS_H};
}

static Rect scan_button_rect() {
    Rect content = content_rect();
    return {(g_win_w - SCAN_BTN_W) / 2, content.y + 12, SCAN_BTN_W, BTN_H};
}

static int devices_list_top() {
    return content_rect().y;
}

static int devices_list_bottom() {
    return status_bar_rect().y;
}

static int scan_list_top() {
    Rect scan_btn = scan_button_rect();
    return scan_btn.y + scan_btn.h + 12;
}

static int scan_list_bottom() {
    return status_bar_rect().y;
}

static Rect devices_row_rect(int index) {
    return {0, devices_list_top() + index * ROW_H - g_scroll, g_win_w, ROW_H};
}

static Rect scan_row_rect(int index) {
    return {0, scan_list_top() + index * ROW_H - g_scroll, g_win_w, ROW_H};
}

static Rect row_action_button_rect(const Rect& row) {
    return {g_win_w - PAD - BTN_W, row.y + (ROW_H - BTN_H) / 2, BTN_W, BTN_H};
}

// Secondary (Forget) button, left of the primary action button. Used for
// paired-but-disconnected rows that show "Connect" + "Forget".
static Rect row_forget_button_rect(const Rect& row) {
    Rect a = row_action_button_rect(row);
    return {a.x - 8 - FORGET_W, a.y, FORGET_W, BTN_H};
}

static int current_row_count() {
    if (g_tab == TAB_DEVICES) return g_device_count;
    if (g_tab == TAB_SCAN) return g_scan_count;
    return 0;   // Adapter tab is not a scrollable list
}

static int current_list_top() {
    return g_tab == TAB_DEVICES ? devices_list_top() : scan_list_top();
}

static int current_list_bottom() {
    return g_tab == TAB_DEVICES ? devices_list_bottom() : scan_list_bottom();
}

static int max_scroll() {
    int visible_h = gui_max(current_list_bottom() - current_list_top(), 0);
    int total_h = current_row_count() * ROW_H;
    if (total_h <= visible_h) return 0;
    return total_h - visible_h;
}

static void clamp_scroll() {
    g_scroll = gui_clamp(g_scroll, 0, max_scroll());
}

static void draw_text_fit(Canvas& c, int x, int y,
                          const char* text, int max_w, Color color) {
    if (!text || !text[0] || max_w <= 4) return;
    if (text_width(text) <= max_w) {
        c.text(x, y, text, color);
        return;
    }

    const char* ellipsis = "...";
    int ell_w = text_width(ellipsis);
    if (ell_w >= max_w) return;

    char buf[96];
    int out = 0;
    while (text[out] && out < (int)sizeof(buf) - 4) {
        buf[out] = text[out];
        buf[out + 1] = '\0';
        if (text_width(buf) + ell_w > max_w)
            break;
        out++;
    }

    if (out <= 0) return;
    buf[out] = '.';
    buf[out + 1] = '.';
    buf[out + 2] = '.';
    buf[out + 3] = '\0';
    c.text(x, y, buf, color);
}

static void draw_centered_text(Canvas& c, int area_y, int area_h, const char* text, Color color) {
    int tw = text_width(text);
    int th = system_font_height();
    c.text((g_win_w - tw) / 2, area_y + (area_h - th) / 2, text, color);
}

static bool mouse_in_rect(const Rect& rect) {
    return rect.contains(g_mouse_x, g_mouse_y);
}

static mtk::WidgetState button_state(const Rect& rect, bool enabled, bool active = false) {
    return mtk::widget_state(active, mouse_in_rect(rect), enabled);
}

// ============================================================================
// Bluetooth helpers
// ============================================================================

static void set_status(const char* msg) {
    snprintf(g_status, sizeof(g_status), "%s", msg);
    g_status_time = montauk::get_milliseconds();
}

static void refresh_adapter() {
    montauk::memset(&g_adapter, 0, sizeof(g_adapter));
    int r = montauk::bt_info(&g_adapter);
    g_adapter_ok = (r >= 0 && g_adapter.initialized);
}

static void refresh_devices() {
    int active = montauk::bt_list(g_devices, MAX_CONNECTED);
    if (active < 0) active = 0;
    g_device_count = active;

    // Append bonded (paired) devices that aren't currently connected, so the
    // Devices tab remembers them across sessions/reboots.
    montauk::abi::BtBondInfo bonds[8];
    int nb = montauk::bt_bonds(bonds, 8);
    for (int i = 0; i < nb && g_device_count < MAX_CONNECTED; i++) {
        bool already = false;
        for (int j = 0; j < active; j++) {
            if (memcmp(g_devices[j].bdAddr, bonds[i].bdAddr, 6) == 0) { already = true; break; }
        }
        if (already) continue;

        montauk::abi::BtDevInfo d = {};
        montauk::memcpy(d.bdAddr, bonds[i].bdAddr, 6);
        d.connected = 0;        // paired but not connected
        d.encrypted = 0;
        d.handle = 0;
        d.linkType = 0x01;
        g_devices[g_device_count++] = d;
    }

    for (int i = 0; i < g_device_count; i++) {
        bool found = false;
        for (int j = 0; j < g_scan_count; j++) {
            if (memcmp(g_devices[i].bdAddr, g_scan[j].bdAddr, 6) == 0 && g_scan[j].name[0]) {
                montauk::memcpy(g_device_names[i], g_scan[j].name, 64);
                found = true;
                break;
            }
        }
        if (!found) {
            snprintf(g_device_names[i], 64, "%02X:%02X:%02X:%02X:%02X:%02X",
                     g_devices[i].bdAddr[0], g_devices[i].bdAddr[1],
                     g_devices[i].bdAddr[2], g_devices[i].bdAddr[3],
                     g_devices[i].bdAddr[4], g_devices[i].bdAddr[5]);
        }
    }
}

static void do_scan() {
    g_scanning = true;
    g_scan_count = 0;
    g_scroll = 0;
    g_hover_row = -1;
    set_status("Scanning...");
}

static void finish_scan() {
    int r = montauk::bt_scan(g_scan, MAX_SCAN, 3000);
    g_scan_count = r > 0 ? r : 0;
    g_scanning = false;
    clamp_scroll();
    char msg[64];
    snprintf(msg, sizeof(msg), "Found %d device%s", g_scan_count, g_scan_count == 1 ? "" : "s");
    set_status(msg);
}

static void do_connect(const uint8_t* addr) {
    int r = montauk::bt_connect(addr);
    if (r >= 0) set_status("Connected");
    else set_status("Connection failed");
    refresh_devices();
    clamp_scroll();
}

static void do_disconnect(const uint8_t* addr) {
    int r = montauk::bt_disconnect(addr);
    if (r >= 0) set_status("Disconnected");
    else set_status("Disconnect failed");
    refresh_devices();
    clamp_scroll();
}

static void do_forget(const uint8_t* addr) {
    int r = montauk::bt_forget(addr);
    if (r >= 0) set_status("Device forgotten");
    else set_status("Forget failed");
    refresh_devices();
    clamp_scroll();
}

static bool is_connected(const uint8_t* addr) {
    for (int i = 0; i < g_device_count; i++) {
        if (memcmp(g_devices[i].bdAddr, addr, 6) == 0 && g_devices[i].connected)
            return true;
    }
    return false;
}

// ============================================================================
// Format helpers
// ============================================================================

static const char* device_class_str(uint32_t cod) {
    uint32_t major = (cod >> 8) & 0x1F;
    switch (major) {
    case 1:  return "Computer";
    case 2:  return "Phone";
    case 3:  return "Network";
    case 4:  return "Audio/Video";
    case 5:  return "Peripheral";
    case 6:  return "Imaging";
    case 7:  return "Wearable";
    default: return "Device";
    }
}

static void format_addr(char* buf, int len, const uint8_t* addr) {
    snprintf(buf, len, "%02X:%02X:%02X:%02X:%02X:%02X",
             addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
}

static const char* rssi_bar(int8_t rssi) {
    if (rssi >= -50) return "Strong";
    if (rssi >= -70) return "Good";
    if (rssi >= -85) return "Weak";
    return "Very weak";
}

static void set_tab(Tab tab) {
    if (tab == g_tab) return;
    g_tab = tab;
    g_scroll = 0;
    g_hover_row = -1;
    if (tab != TAB_ADAPTER)
        g_mac_focus = false;
    clamp_scroll();
}

// ============================================================================
// Adapter MAC address
// ============================================================================

static bool is_mac_char(char ch) {
    return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') ||
           (ch >= 'A' && ch <= 'F') || ch == ':';
}

static bool accept_mac_char(char ch, void* userdata) {
    (void)userdata;
    return is_mac_char(ch);
}

static bool hex_nibble(char c, uint8_t& out) {
    if (c >= '0' && c <= '9') { out = (uint8_t)(c - '0'); return true; }
    if (c >= 'a' && c <= 'f') { out = (uint8_t)(c - 'a' + 10); return true; }
    if (c >= 'A' && c <= 'F') { out = (uint8_t)(c - 'A' + 10); return true; }
    return false;
}

// Parse "XX:XX:XX:XX:XX:XX" (':' or '-' separators, optional) into 6 bytes,
// out[0] = first printed octet. Returns false unless exactly six octets and no
// trailing junk remain.
static bool parse_mac(const char* s, uint8_t out[6]) {
    int byteIdx = 0;
    const char* p = s;
    while (byteIdx < 6) {
        while (*p == ':' || *p == '-' || *p == ' ') p++;
        uint8_t hi = 0, lo = 0;
        if (!hex_nibble(p[0], hi) || !hex_nibble(p[1], lo)) return false;
        out[byteIdx++] = (uint8_t)((hi << 4) | lo);
        p += 2;
    }
    while (*p == ':' || *p == '-' || *p == ' ') p++;
    return *p == '\0';
}

static uint32_t rng_next() {
    if (g_rng == 0) g_rng = montauk::get_milliseconds() | 1ULL;
    g_rng ^= g_rng << 13;
    g_rng ^= g_rng >> 7;
    g_rng ^= g_rng << 17;
    return (uint32_t)g_rng;
}

static void randomize_mac() {
    uint8_t b[6];
    for (int i = 0; i < 6; i++) b[i] = (uint8_t)(rng_next() & 0xFF);
    // First printed octet: locally-administered + unicast (clear multicast bit).
    b[0] = (uint8_t)((b[0] & 0xFC) | 0x02);
    format_addr(g_mac_value, sizeof(g_mac_value), b);
    g_mac_focus = true;
    int len = (int)strlen(g_mac_value);
    mtk::text_input_set_cursor(g_mac_input, len, len, false);
}

// Prefill the editor from the saved config (if any), else from the live adapter.
static void load_mac_config() {
    montauk::toml::Doc cfg = montauk::config::load("bluetooth");
    const char* m = cfg.get_string("adapter.mac", "");
    if (m && m[0]) {
        snprintf(g_mac_value, sizeof(g_mac_value), "%s", m);
    } else if (g_adapter_ok) {
        format_addr(g_mac_value, sizeof(g_mac_value), g_adapter.bdAddr);
    } else {
        g_mac_value[0] = '\0';
    }
    cfg.destroy();
}

static void apply_mac() {
    uint8_t bytes[6];
    if (!parse_mac(g_mac_value, bytes)) {
        set_status("Invalid MAC address");
        return;
    }

    if (montauk::bt_set_addr(bytes) < 0) {
        set_status("Failed to set MAC");
        return;
    }

    // Normalize and persist to 0:/config/bluetooth.toml so it is re-applied
    // on every boot by the kernel.
    char norm[24];
    format_addr(norm, sizeof(norm), bytes);
    montauk::toml::Doc cfg = montauk::config::load("bluetooth");
    montauk::config::set_string(&cfg, "adapter.mac", norm);
    montauk::config::save("bluetooth", &cfg);
    cfg.destroy();

    snprintf(g_mac_value, sizeof(g_mac_value), "%s", norm);
    g_mac_focus = false;
    set_status("MAC address changed");
    refresh_adapter();
    refresh_devices();
}

static void use_default_mac() {
    montauk::toml::Doc cfg = montauk::config::load("bluetooth");
    bool had = montauk::config::unset(&cfg, "adapter.mac");
    montauk::config::save("bluetooth", &cfg);
    cfg.destroy();
    set_status(had ? "Factory address restored on reboot" : "No custom MAC set");
}

// ---- Adapter tab layout ----

static int a_cur_label_y() { return content_rect().y + A_PAD_TOP; }
static int a_cur_value_y() { return a_cur_label_y() + system_font_height() + 4; }
static int a_new_label_y() { return a_cur_value_y() + system_font_height() + 20; }

static Rect adapter_input_rect() {
    int y = a_new_label_y() + system_font_height() + 6;
    return {PAD, y, g_win_w - PAD * 2, FIELD_H};
}

static Rect adapter_apply_btn() {
    Rect f = adapter_input_rect();
    return {g_win_w - PAD - BTN_W, f.y + f.h + 16, BTN_W, BTN_H};
}

static Rect adapter_random_btn() {
    Rect a = adapter_apply_btn();
    return {PAD, a.y, 80, BTN_H};
}

static Rect adapter_default_btn() {
    Rect r = adapter_random_btn();
    return {r.x + r.w + 8, r.y, 110, BTN_H};
}

// ============================================================================
// Hover and hit testing
// ============================================================================

static int get_hover_row(int mx, int my) {
    if (g_tab == TAB_DEVICES) {
        for (int i = 0; i < g_device_count; i++) {
            if (devices_row_rect(i).contains(mx, my))
                return i;
        }
    } else if (g_tab == TAB_SCAN) {
        for (int i = 0; i < g_scan_count; i++) {
            if (scan_row_rect(i).contains(mx, my))
                return i;
        }
    }
    return -1;
}

static bool handle_click(int mx, int my) {
    int tab = mtk::hit_tab_bar(tab_bar_rect(), TAB_COUNT, mx, my);
    if (tab >= 0) {
        set_tab((Tab)tab);
        return true;
    }

    if (g_tab == TAB_SCAN) {
        Rect scan_btn = scan_button_rect();
        if (!g_scanning && scan_btn.contains(mx, my)) {
            do_scan();
            return true;
        }

        for (int i = 0; i < g_scan_count; i++) {
            Rect row = scan_row_rect(i);
            if (!row.contains(mx, my)) continue;

            Rect action = row_action_button_rect(row);
            if (action.contains(mx, my)) {
                if (is_connected(g_scan[i].bdAddr))
                    do_disconnect(g_scan[i].bdAddr);
                else
                    do_connect(g_scan[i].bdAddr);
                return true;
            }
        }
    } else {
        for (int i = 0; i < g_device_count; i++) {
            Rect row = devices_row_rect(i);
            if (!row.contains(mx, my)) continue;

            Rect action = row_action_button_rect(row);
            if (g_devices[i].connected) {
                if (action.contains(mx, my)) {
                    do_disconnect(g_devices[i].bdAddr);
                    return true;
                }
            } else {
                // Paired but not connected: Connect (primary) + Forget.
                Rect forget = row_forget_button_rect(row);
                if (action.contains(mx, my)) {
                    do_connect(g_devices[i].bdAddr);
                    return true;
                }
                if (forget.contains(mx, my)) {
                    do_forget(g_devices[i].bdAddr);
                    return true;
                }
            }
        }
    }

    return false;
}

// Adapter-tab pointer handling: text field (focus/cursor/selection/context)
// plus the Apply / Random / Use Default buttons. The tab bar is handled by the
// caller before this runs. Returns true if the view should be redrawn.
static bool handle_adapter_mouse(int mx, int my, uint8_t buttons,
                                 uint8_t prev_buttons, int view_w, int view_h) {
    bool left_press = (buttons & 1) && !(prev_buttons & 1);
    Rect field = adapter_input_rect();

    // While a context menu is open or a drag is in progress, let the widget
    // consume the event regardless of where the pointer is.
    if (g_mac_input.context.open || g_mac_input.dragging) {
        int res = mtk::text_input_handle_mouse(
            g_mac_input, field, g_mac_value, (int)sizeof(g_mac_value),
            mx, my, buttons, prev_buttons, view_w, view_h,
            g_mac_focus, false, accept_mac_char);
        return res != mtk::TEXT_INPUT_NONE;
    }

    if (left_press) {
        if (g_adapter_ok && adapter_apply_btn().contains(mx, my)) { apply_mac(); return true; }
        if (g_adapter_ok && adapter_random_btn().contains(mx, my)) { randomize_mac(); return true; }
        if (adapter_default_btn().contains(mx, my)) { use_default_mac(); return true; }
    }

    if (field.contains(mx, my)) {
        g_mac_focus = true;
        mtk::text_input_handle_mouse(
            g_mac_input, field, g_mac_value, (int)sizeof(g_mac_value),
            mx, my, buttons, prev_buttons, view_w, view_h,
            true, false, accept_mac_char);
        return true;
    }

    if (left_press) {
        g_mac_focus = false;
        return true;
    }
    return false;
}

static bool handle_adapter_key(const montauk::abi::KeyEvent& key) {
    if (key.ascii == '\n' || key.ascii == '\r') { apply_mac(); return true; }
    if (key.ascii == '\t') { g_mac_focus = !g_mac_focus; return true; }

    if (g_mac_focus) {
        int res = mtk::text_input_key(g_mac_input, g_mac_value,
                                      (int)sizeof(g_mac_value), key, accept_mac_char);
        return res != mtk::TEXT_INPUT_NONE;
    }

    // Not editing: number keys still switch tabs; any MAC char starts editing.
    if (key.ascii == '1') { set_tab(TAB_DEVICES); return true; }
    if (key.ascii == '2') { set_tab(TAB_SCAN); return true; }
    if (key.ascii == '3') { return true; }
    if (is_mac_char(key.ascii)) {
        g_mac_focus = true;
        int res = mtk::text_input_key(g_mac_input, g_mac_value,
                                      (int)sizeof(g_mac_value), key, accept_mac_char);
        return res != mtk::TEXT_INPUT_NONE;
    }
    return false;
}

// ============================================================================
// Render
// ============================================================================

static void render_devices_tab(Canvas& canvas, const mtk::Theme& theme) {
    Rect list = {0, devices_list_top(), g_win_w, gui_max(devices_list_bottom() - devices_list_top(), 0)};
    int fh = system_font_height();

    if (g_device_count == 0) {
        draw_centered_text(canvas, list.y, list.h, "No paired devices", theme.text_subtle);
        return;
    }

    for (int i = 0; i < g_device_count; i++) {
        Rect row = devices_row_rect(i);
        if (row.y + row.h <= list.y || row.y >= list.y + list.h) continue;

        if (g_hover_row == i)
            canvas.fill_rect(row.x, row.y, row.w, row.h, theme.surface_hover);
        else
            mtk::draw_list_row(canvas, row, false, (i & 1) != 0, theme);

        bool connected = g_devices[i].connected;
        Color dot_color = connected ? GREEN : theme.text_subtle;
        fill_circle(canvas, PAD + 6, row.y + ROW_H / 2, 5, dot_color);

        Rect action = row_action_button_rect(row);
        Rect forget = row_forget_button_rect(row);
        int text_x = PAD + 20;
        // Connected rows have one button (Disconnect); paired rows have two
        // (Connect + Forget), so the text must stop before the leftmost button.
        int text_w_max = (connected ? action.x : forget.x) - text_x - 10;
        draw_text_fit(canvas, text_x, row.y + 8, g_device_names[i], text_w_max, theme.text);

        char addr_str[24];
        format_addr(addr_str, sizeof(addr_str), g_devices[i].bdAddr);
        draw_text_fit(canvas, text_x, row.y + 8 + fh + 2, addr_str, text_w_max, theme.text_subtle);

        if (connected) {
            mtk::draw_button(canvas, action, "Disconnect", mtk::BUTTON_DANGER,
                             button_state(action, true), theme);
        } else {
            mtk::draw_button(canvas, action, "Connect", mtk::BUTTON_PRIMARY,
                             button_state(action, true), theme);
            mtk::draw_button(canvas, forget, "Forget", mtk::BUTTON_SECONDARY,
                             button_state(forget, true), theme);
        }

        mtk::draw_separator(canvas, PAD, row.y + ROW_H - 1, g_win_w - 2 * PAD, theme);
    }
}

static void render_scan_tab(Canvas& canvas, const mtk::Theme& theme) {
    Rect scan_btn = scan_button_rect();
    Rect list = {0, scan_list_top(), g_win_w, gui_max(scan_list_bottom() - scan_list_top(), 0)};
    int fh = system_font_height();

    if (g_scanning) {
        mtk::draw_button(canvas, scan_btn, "Scanning...", mtk::BUTTON_SECONDARY,
                         button_state(scan_btn, false), theme);
    } else {
        mtk::draw_button(canvas, scan_btn, "Scan", mtk::BUTTON_PRIMARY,
                         button_state(scan_btn, true), theme);
    }

    if (g_scan_count == 0 && !g_scanning) {
        draw_centered_text(canvas, list.y, list.h, "Press Scan to find devices", theme.text_subtle);
        return;
    }

    for (int i = 0; i < g_scan_count; i++) {
        Rect row = scan_row_rect(i);
        if (row.y + row.h <= list.y || row.y >= list.y + list.h) continue;

        if (g_hover_row == i)
            canvas.fill_rect(row.x, row.y, row.w, row.h, theme.surface_hover);
        else
            mtk::draw_list_row(canvas, row, false, (i & 1) != 0, theme);

        fill_circle(canvas, PAD + 6, row.y + ROW_H / 2, 5, theme.accent);

        Rect action = row_action_button_rect(row);
        int text_x = PAD + 20;
        int text_w_max = action.x - text_x - 10;

        const char* display_name = g_scan[i].name[0] ? g_scan[i].name : "Unknown Device";
        draw_text_fit(canvas, text_x, row.y + 4, display_name, text_w_max, theme.text);

        char detail[80];
        char addr_str[24];
        format_addr(addr_str, sizeof(addr_str), g_scan[i].bdAddr);
        snprintf(detail, sizeof(detail), "%s  |  %s  |  %s",
                 device_class_str(g_scan[i].classOfDevice), addr_str, rssi_bar(g_scan[i].rssi));
        draw_text_fit(canvas, text_x, row.y + 4 + fh + 2, detail, text_w_max, theme.text_subtle);

        bool conn = is_connected(g_scan[i].bdAddr);
        mtk::draw_button(canvas, action, conn ? "Disconnect" : "Connect",
                         conn ? mtk::BUTTON_DANGER : mtk::BUTTON_PRIMARY,
                         button_state(action, true), theme);

        mtk::draw_separator(canvas, PAD, row.y + ROW_H - 1, g_win_w - 2 * PAD, theme);
    }
}

static void render_adapter_tab(Canvas& canvas, const mtk::Theme& theme) {
    // Current address
    canvas.text(PAD, a_cur_label_y(), "Current Address", theme.text_subtle);
    if (g_adapter_ok) {
        char cur[24];
        format_addr(cur, sizeof(cur), g_adapter.bdAddr);
        canvas.text(PAD, a_cur_value_y(), cur, theme.text);
    } else {
        canvas.text(PAD, a_cur_value_y(), "No adapter found", theme.danger);
    }

    // Editable new address
    canvas.text(PAD, a_new_label_y(), "New Address", theme.text_muted);
    Rect field = adapter_input_rect();
    mtk::draw_text_field(canvas, field, g_mac_value, g_mac_input.cursor,
                         g_mac_focus, false, theme, g_mac_input.selection_anchor);

    bool enabled = g_adapter_ok;
    Rect apply = adapter_apply_btn();
    Rect rnd = adapter_random_btn();
    Rect def = adapter_default_btn();
    mtk::draw_button(canvas, apply, "Apply", mtk::BUTTON_PRIMARY,
                     button_state(apply, enabled), theme);
    mtk::draw_button(canvas, rnd, "Random", mtk::BUTTON_SECONDARY,
                     button_state(rnd, enabled), theme);
    mtk::draw_button(canvas, def, "Use Default", mtk::BUTTON_SECONDARY,
                     button_state(def, true), theme);

    draw_text_fit(canvas, PAD, apply.y + BTN_H + 16,
                  "Saved to config and re-applied on boot.",
                  g_win_w - PAD * 2, theme.text_subtle);

    // Right-click context menu (cut/copy/paste) for the field, drawn last.
    mtk::draw_text_input_context_menu(
        canvas, g_mac_input, theme,
        mtk::text_input_has_selection(g_mac_input, g_mac_value,
                                      (int)sizeof(g_mac_value)));
}

static void render_status(Canvas& canvas, const mtk::Theme& theme) {
    Rect status = status_bar_rect();
    int sy = status.y + (status.h - system_font_height()) / 2;
    uint64_t now = montauk::get_milliseconds();

    canvas.fill_rect(status.x, status.y, status.w, status.h, theme.surface);
    mtk::draw_separator(canvas, 0, status.y, g_win_w, theme);

    int status_w = 0;
    bool show_status = g_status[0] && (now - g_status_time) < 5000;
    if (show_status)
        status_w = text_width(g_status);

    int left_max = g_win_w - PAD * 2 - (show_status ? status_w + 12 : 0);
    if (g_adapter_ok) {
        int connected = 0;
        for (int i = 0; i < g_device_count; i++)
            if (g_devices[i].connected) connected++;

        char adapter_info[96];
        char addr_str[24];
        format_addr(addr_str, sizeof(addr_str), g_adapter.bdAddr);
        const char* name = g_adapter.name[0] ? g_adapter.name : "Adapter";
        snprintf(adapter_info, sizeof(adapter_info), "%s  |  %s  |  %d connected",
                 name, addr_str, connected);
        draw_text_fit(canvas, PAD, sy, adapter_info, left_max, theme.text_subtle);
    } else {
        draw_text_fit(canvas, PAD, sy, "No adapter found", left_max, theme.danger);
    }

    if (show_status)
        canvas.text(g_win_w - PAD - status_w, sy, g_status, theme.text_subtle);
}

static void render(Canvas& canvas) {
    mtk::Theme theme = bt_theme();
    canvas.fill(theme.window_bg);
    mtk::draw_tab_bar(canvas, tab_bar_rect(), g_tab_labels, TAB_COUNT, g_tab, theme);

    if (g_tab == TAB_DEVICES)
        render_devices_tab(canvas, theme);
    else if (g_tab == TAB_SCAN)
        render_scan_tab(canvas, theme);
    else
        render_adapter_tab(canvas, theme);

    render_status(canvas, theme);
}

// ============================================================================
// Entry point
// ============================================================================

extern "C" void _start() {
    if (!fonts::init())
        montauk::exit(1);

    refresh_adapter();
    refresh_devices();
    load_mac_config();
    clamp_scroll();
    g_status[0] = 0;

    WsWindow win;
    if (!win.create("Bluetooth", WIN_W, WIN_H))
        montauk::exit(1);

    mtk::StandaloneHost host(&win);
    Canvas canvas = host.canvas();
    render(canvas);
    host.present();

    uint64_t last_refresh = montauk::get_milliseconds();

    while (true) {
        montauk::abi::WinEvent ev;
        int r = win.poll(&ev);

        if (r < 0) break;

        if (g_scanning) {
            canvas = host.canvas();
            render(canvas);
            host.present();
            finish_scan();
            refresh_devices();
            g_hover_row = get_hover_row(g_mouse_x, g_mouse_y);
            canvas = host.canvas();
            render(canvas);
            host.present();
            continue;
        }

        if (r == 0) {
            uint64_t now = montauk::get_milliseconds();
            if (now - last_refresh >= 5000) {
                refresh_adapter();
                refresh_devices();
                clamp_scroll();
                g_hover_row = get_hover_row(g_mouse_x, g_mouse_y);
                last_refresh = now;
                canvas = host.canvas();
                render(canvas);
                host.present();
            }
            montauk::sleep_ms(16);
            continue;
        }

        bool redraw = false;

        if (ev.type == 3) break;

        if (ev.type == 2) {
            g_win_w = win.width;
            g_win_h = win.height;
            clamp_scroll();
            g_hover_row = get_hover_row(g_mouse_x, g_mouse_y);
            redraw = true;
        }

        if (ev.type == 0 && ev.key.pressed) {
            if (g_tab == TAB_ADAPTER) {
                // Esc defocuses the field if editing, otherwise closes.
                if (ev.key.scancode == 0x01 && g_mac_focus) {
                    g_mac_focus = false;
                    redraw = true;
                } else if (ev.key.scancode == 0x01) {
                    break;
                } else if (handle_adapter_key(ev.key)) {
                    redraw = true;
                }
            } else {
                if (ev.key.scancode == 0x01) break;
                if ((ev.key.ascii == 's' || ev.key.ascii == 'S') && !g_scanning) {
                    do_scan();
                    redraw = true;
                }
                if (ev.key.ascii == '1') {
                    set_tab(TAB_DEVICES);
                    redraw = true;
                }
                if (ev.key.ascii == '2') {
                    set_tab(TAB_SCAN);
                    redraw = true;
                }
                if (ev.key.ascii == '3') {
                    set_tab(TAB_ADAPTER);
                    redraw = true;
                }
            }
        }

        if (ev.type == 1) {
            bool moved = (ev.mouse.x != g_mouse_x) || (ev.mouse.y != g_mouse_y);
            g_mouse_x = ev.mouse.x;
            g_mouse_y = ev.mouse.y;

            if (g_tab == TAB_ADAPTER) {
                if (moved) redraw = true;
                bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
                int tab = clicked
                    ? mtk::hit_tab_bar(tab_bar_rect(), TAB_COUNT, g_mouse_x, g_mouse_y)
                    : -1;
                if (tab >= 0) {
                    set_tab((Tab)tab);
                    redraw = true;
                } else if (handle_adapter_mouse(g_mouse_x, g_mouse_y,
                                                ev.mouse.buttons, ev.mouse.prev_buttons,
                                                win.width, win.height)) {
                    redraw = true;
                }
            } else {
                int new_hover = get_hover_row(g_mouse_x, g_mouse_y);
                if (new_hover != g_hover_row) {
                    g_hover_row = new_hover;
                    redraw = true;
                }

                if (moved)
                    redraw = true;

                bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
                if (clicked && handle_click(ev.mouse.x, ev.mouse.y))
                    redraw = true;

                if (ev.mouse.scroll) {
                    g_scroll -= ev.mouse.scroll * SCROLL_STEP;
                    clamp_scroll();
                    g_hover_row = get_hover_row(g_mouse_x, g_mouse_y);
                    redraw = true;
                }
            }
        }

        if (redraw) {
            canvas = host.canvas();
            render(canvas);
            host.present();
        }
    }

    win.destroy();
    montauk::exit(0);
}
