/*
 * main.cpp
 * MontaukOS Time Zone configuration applet
 * Copyright (c) 2026 Daniel Hammer
 */

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

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

using namespace gui;

static constexpr int INIT_W = 640;
static constexpr int INIT_H = 460;
static constexpr int HEADER_H = 14;
static constexpr int FOOTER_H = 58;
static constexpr int PAD = 16;
static constexpr int GAP = 12;
static constexpr int LABEL_H = 24;
static constexpr int ROW_H = 30;
static constexpr int MAX_COUNTRIES = 320;

struct CountryEntry {
    char key[64];     // Full TOML table key, e.g. countries.NO
    char code[24];    // Country code suffix, e.g. NO
    const char* name;
    const char* default_tz;
    const char* note;
    bool dst_enabled;
    char dst_rule[32];
    montauk::toml::Value* zones;
};

static WsWindow g_win;
static montauk::toml::Doc g_data;
static bool g_data_loaded = false;
static CountryEntry g_countries[MAX_COUNTRIES];
static int g_country_count = 0;

static int g_selected_country = 0;
static int g_selected_zone = 0;
static int g_saved_country = 0;
static int g_saved_zone = 0;
static int g_country_scroll = 0;
static int g_zone_scroll = 0;
static int g_focus_pane = 0; // 0=country list, 1=zone list
static int g_mouse_x = -1;
static int g_mouse_y = -1;
static bool g_dirty = false;
static char g_status[160] = {};
static uint64_t g_status_time = 0;
static Color g_accent = colors::ACCENT;

static void safe_copy(char* dst, int cap, const char* src) {
    montauk::strncpy(dst, src ? src : "", cap);
}

static int str_compare_ci(const char* a, const char* b) {
    while (*a && *b) {
        char ca = (*a >= 'A' && *a <= 'Z') ? (*a + 32) : *a;
        char cb = (*b >= 'A' && *b <= 'Z') ? (*b + 32) : *b;
        if (ca != cb) return ca - cb;
        a++;
        b++;
    }
    return (unsigned char)*a - (unsigned char)*b;
}

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

static bool status_visible() {
    return g_status[0] && (montauk::get_milliseconds() - g_status_time < 5000);
}

static mtk::Theme app_theme() {
    mtk::Theme theme = mtk::make_theme(g_accent);
    theme.disabled_bg = Color::from_rgb(0xE4, 0xE4, 0xE4);
    theme.disabled_fg = Color::from_rgb(0x9A, 0x9A, 0x9A);
    return theme;
}

static void apply_scale(int scale) {
    switch (scale) {
    case 0:
        fonts::UI_SIZE = 14;
        fonts::TITLE_SIZE = 14;
        fonts::TERM_SIZE = 14;
        fonts::LARGE_SIZE = 22;
        break;
    case 2:
        fonts::UI_SIZE = 22;
        fonts::TITLE_SIZE = 22;
        fonts::TERM_SIZE = 22;
        fonts::LARGE_SIZE = 34;
        break;
    default:
        fonts::UI_SIZE = 18;
        fonts::TITLE_SIZE = 18;
        fonts::TERM_SIZE = 18;
        fonts::LARGE_SIZE = 28;
        break;
    }
}

static void load_accent() {
    g_accent = mtk::load_system_accent();
}

static void build_key(char* out, int cap, const char* base, const char* field) {
    snprintf(out, cap, "%s.%s", base, field);
}

static const char* zone_name(const CountryEntry& country, int index) {
    if (!country.zones || index < 0 || index >= country.zones->array.count) return "";
    montauk::toml::Value* item = country.zones->array.items[index];
    if (!item || item->type != montauk::toml::Type::String) return "";

    if (country.zones->type == montauk::toml::Type::Array)
        return item->str ? item->str : "";

    char prefix[96];
    snprintf(prefix, sizeof(prefix), "%s.timezones.", country.key);
    return (item->key && montauk::starts_with(item->key, prefix))
        ? item->key + montauk::slen(prefix)
        : "";
}

static int zone_count(const CountryEntry& country) {
    return country.zones ? country.zones->array.count : 0;
}

static int find_zone_index(const CountryEntry& country, const char* zone) {
    if (!zone || !zone[0]) return -1;
    int count = zone_count(country);
    for (int i = 0; i < count; i++) {
        if (montauk::streq(zone_name(country, i), zone))
            return i;
    }
    return -1;
}

static int default_zone_index(const CountryEntry& country) {
    int index = find_zone_index(country, country.default_tz);
    return index >= 0 ? index : 0;
}

static const char* zone_offset_string(const CountryEntry& country, int index) {
    if (!country.zones || country.zones->type != montauk::toml::Type::Table)
        return "";
    if (index < 0 || index >= country.zones->array.count)
        return "";

    montauk::toml::Value* item = country.zones->array.items[index];
    if (!item || item->type != montauk::toml::Type::String)
        return "";
    return item->str ? item->str : "";
}

static bool parse_offset_minutes(const char* value, int* out_minutes) {
    if (!value || !value[0]) return false;

    int sign = 1;
    int pos = 0;
    if (value[pos] == '+') {
        pos++;
    } else if (value[pos] == '-') {
        sign = -1;
        pos++;
    }

    if (value[pos] < '0' || value[pos] > '9') return false;
    int hours = 0;
    while (value[pos] >= '0' && value[pos] <= '9') {
        hours = hours * 10 + (value[pos] - '0');
        pos++;
    }

    int minutes = 0;
    if (value[pos] == ':') {
        pos++;
        if (value[pos] < '0' || value[pos] > '9') return false;
        while (value[pos] >= '0' && value[pos] <= '9') {
            minutes = minutes * 10 + (value[pos] - '0');
            pos++;
        }
    }

    if (value[pos] != '\0') return false;
    if (hours > 24 || minutes > 59) return false;

    if (out_minutes) *out_minutes = sign * (hours * 60 + minutes);
    return true;
}

static int visible_rows(const Rect& list) {
    int rows = list.h / ROW_H;
    return rows > 0 ? rows : 1;
}

static void clamp_scrolls() {
    Rect country_list = {};
    Rect zone_list = {};
    int left_w = gui_min(250, (g_win.width - PAD * 2 - GAP) / 2);
    int list_y = HEADER_H + LABEL_H;
    int list_h = gui_max(g_win.height - HEADER_H - FOOTER_H - LABEL_H - PAD, ROW_H);
    country_list = {PAD, list_y, left_w, list_h};
    zone_list = {PAD + left_w + GAP, list_y,
                 g_win.width - PAD * 2 - GAP - left_w, list_h};

    int country_max = gui_max(0, g_country_count - visible_rows(country_list));
    if (g_country_scroll < 0) g_country_scroll = 0;
    if (g_country_scroll > country_max) g_country_scroll = country_max;

    int zcount = (g_selected_country >= 0 && g_selected_country < g_country_count)
        ? zone_count(g_countries[g_selected_country]) : 0;
    int zone_max = gui_max(0, zcount - visible_rows(zone_list));
    if (g_zone_scroll < 0) g_zone_scroll = 0;
    if (g_zone_scroll > zone_max) g_zone_scroll = zone_max;
}

static void ensure_country_visible() {
    Rect list = {PAD, HEADER_H + LABEL_H, 250,
                 gui_max(g_win.height - HEADER_H - FOOTER_H - LABEL_H - PAD, ROW_H)};
    int rows = visible_rows(list);
    if (g_selected_country < g_country_scroll)
        g_country_scroll = g_selected_country;
    else if (g_selected_country >= g_country_scroll + rows)
        g_country_scroll = g_selected_country - rows + 1;
}

static void ensure_zone_visible() {
    int left_w = gui_min(250, (g_win.width - PAD * 2 - GAP) / 2);
    Rect list = {PAD + left_w + GAP, HEADER_H + LABEL_H,
                 g_win.width - PAD * 2 - GAP - left_w,
                 gui_max(g_win.height - HEADER_H - FOOTER_H - LABEL_H - PAD, ROW_H)};
    int rows = visible_rows(list);
    if (g_selected_zone < g_zone_scroll)
        g_zone_scroll = g_selected_zone;
    else if (g_selected_zone >= g_zone_scroll + rows)
        g_zone_scroll = g_selected_zone - rows + 1;
}

static int find_country_by_code(const char* code) {
    if (!code || !code[0]) return -1;
    for (int i = 0; i < g_country_count; i++) {
        if (montauk::streq(g_countries[i].code, code))
            return i;
    }
    return -1;
}

static void sort_countries() {
    for (int i = 1; i < g_country_count; i++) {
        CountryEntry tmp = g_countries[i];
        int j = i - 1;
        while (j >= 0 && str_compare_ci(tmp.name, g_countries[j].name) < 0) {
            g_countries[j + 1] = g_countries[j];
            j--;
        }
        g_countries[j + 1] = tmp;
    }
}

static void load_timezone_data() {
    g_data = montauk::config::load("timezonedata");
    g_data_loaded = true;
    g_country_count = 0;

    static constexpr const char* prefix = "countries.";
    int prefix_len = montauk::slen(prefix);

    for (int i = 0; i < g_data.entries.count && g_country_count < MAX_COUNTRIES; i++) {
        montauk::toml::Value* item = g_data.entries.items[i];
        if (!item || item->type != montauk::toml::Type::Table || !item->key) continue;
        if (!montauk::starts_with(item->key, prefix)) continue;

        CountryEntry entry = {};
        safe_copy(entry.key, sizeof(entry.key), item->key);
        safe_copy(entry.code, sizeof(entry.code), item->key + prefix_len);

        char key[128];
        build_key(key, sizeof(key), item->key, "name");
        entry.name = g_data.get_string(key, entry.code);

        build_key(key, sizeof(key), item->key, "timezones");
        entry.zones = g_data.get(key);
        if (!entry.zones || entry.zones->array.count <= 0) continue;
        if (entry.zones->type != montauk::toml::Type::Array &&
            entry.zones->type != montauk::toml::Type::Table) {
            continue;
        }

        build_key(key, sizeof(key), item->key, "default_tz");
        entry.default_tz = g_data.get_string(key, zone_name(entry, 0));

        build_key(key, sizeof(key), item->key, "dst_enabled");
        entry.dst_enabled = g_data.get_bool(key, false);

        build_key(key, sizeof(key), item->key, "dst_rules.rule");
        safe_copy(entry.dst_rule, sizeof(entry.dst_rule), g_data.get_string(key, ""));

        build_key(key, sizeof(key), item->key, "note");
        entry.note = g_data.get_string(key, "");

        g_countries[g_country_count++] = entry;
    }

    sort_countries();
}

static void load_saved_selection() {
    char saved_code[48] = "NO";
    char saved_zone[96] = "";

    auto cfg = montauk::config::load("timezone");
    safe_copy(saved_code, sizeof(saved_code),
              cfg.get_string("timezone.country_code", saved_code));
    safe_copy(saved_zone, sizeof(saved_zone),
              cfg.get_string("timezone.zone", ""));
    cfg.destroy();

    int country = find_country_by_code(saved_code);
    if (country < 0) country = find_country_by_code("NO");
    if (country < 0 && g_country_count > 0) country = 0;

    g_selected_country = country >= 0 ? country : 0;
    if (g_country_count > 0) {
        CountryEntry& c = g_countries[g_selected_country];
        int zone = find_zone_index(c, saved_zone);
        if (zone < 0) zone = default_zone_index(c);
        g_selected_zone = zone;
    } else {
        g_selected_zone = 0;
    }

    g_saved_country = g_selected_country;
    g_saved_zone = g_selected_zone;
    g_dirty = false;
    ensure_country_visible();
    ensure_zone_visible();
    clamp_scrolls();
}

static void fit_text(char* out, int cap, const char* text, int max_w) {
    safe_copy(out, cap, text);
    if (text_width(out) <= max_w) return;

    static constexpr const char* ellipsis = "...";
    int len = montauk::slen(out);
    while (len > 0) {
        out[--len] = '\0';
        char candidate[160];
        snprintf(candidate, sizeof(candidate), "%s%s", out, ellipsis);
        if (text_width(candidate) <= max_w) {
            safe_copy(out, cap, candidate);
            return;
        }
    }
    safe_copy(out, cap, ellipsis);
}

static void format_offset(int minutes, char* out, int cap) {
    int h = minutes / 60;
    int m = minutes % 60;
    if (m < 0) m = -m;
    if (m) snprintf(out, cap, "UTC%s%d:%02d", h >= 0 ? "+" : "", h, m);
    else snprintf(out, cap, "UTC%s%d", h >= 0 ? "+" : "", h);
}

static bool selected_offset_minutes(int* out_minutes) {
    if (g_selected_country < 0 || g_selected_country >= g_country_count)
        return false;

    CountryEntry& country = g_countries[g_selected_country];
    char key[128];

    const char* offset = zone_offset_string(country, g_selected_zone);
    if (parse_offset_minutes(offset, out_minutes))
        return true;

    build_key(key, sizeof(key), country.key, "offsets");
    montauk::toml::Value* offsets = g_data.get_array(key);
    if (offsets && g_selected_zone >= 0 && g_selected_zone < offsets->array.count) {
        montauk::toml::Value* item = offsets->array.items[g_selected_zone];
        if (item && item->type == montauk::toml::Type::Int) {
            if (out_minutes) *out_minutes = (int)item->ival;
            return true;
        }
    }

    build_key(key, sizeof(key), country.key, "offset_minutes");
    montauk::toml::Value* single = g_data.get(key);
    if (single && single->type == montauk::toml::Type::Int) {
        if (out_minutes) *out_minutes = (int)single->ival;
        return true;
    }

    return false;
}

static int dst_offset_change_minutes(const CountryEntry& country) {
    if (!country.dst_enabled || !country.dst_rule[0])
        return 0;

    char key[96];
    snprintf(key, sizeof(key), "dst_rules.%s.offset_change", country.dst_rule);
    return (int)(g_data.get_int(key, 0) / 60);
}

static void save_selection() {
    if (g_country_count <= 0) return;
    CountryEntry& country = g_countries[g_selected_country];
    const char* zone = zone_name(country, g_selected_zone);

    auto cfg = montauk::config::load("timezone");
    montauk::config::set_string(&cfg, "timezone.country_code", country.code);
    montauk::config::set_string(&cfg, "timezone.country", country.name);
    montauk::config::set_string(&cfg, "timezone.zone", zone);
    montauk::config::set_bool(&cfg, "timezone.dst_enabled", country.dst_enabled);
    if (country.dst_rule[0])
        montauk::config::set_string(&cfg, "timezone.dst_rule", country.dst_rule);
    else
        montauk::config::unset(&cfg, "timezone.dst_rule");

    int dst_change = dst_offset_change_minutes(country);
    if (country.dst_enabled && dst_change != 0)
        montauk::config::set_int(&cfg, "timezone.dst_offset_minutes", dst_change);
    else
        montauk::config::unset(&cfg, "timezone.dst_offset_minutes");

    int offset = 0;
    bool has_offset = selected_offset_minutes(&offset);
    if (has_offset) {
        const char* offset_text = zone_offset_string(country, g_selected_zone);
        if (offset_text[0])
            montauk::config::set_string(&cfg, "timezone.offset", offset_text);
        montauk::config::set_int(&cfg, "timezone.offset_minutes", offset);
        montauk::settz(offset);
    } else {
        montauk::config::unset(&cfg, "timezone.offset");
    }

    int ret = montauk::config::save("timezone", &cfg);
    cfg.destroy();

    if (ret < 0) {
        set_status("Could not save time zone");
        return;
    }

    g_saved_country = g_selected_country;
    g_saved_zone = g_selected_zone;
    g_dirty = false;
    set_status(has_offset ? "Saved and applied" : "Saved selection; clock offset unchanged");
}

static void revert_selection() {
    g_selected_country = g_saved_country;
    g_selected_zone = g_saved_zone;
    g_dirty = false;
    ensure_country_visible();
    ensure_zone_visible();
    clamp_scrolls();
    set_status("Selection reverted");
}

static Rect country_list_rect() {
    int left_w = gui_min(250, (g_win.width - PAD * 2 - GAP) / 2);
    return {PAD, HEADER_H + LABEL_H, left_w,
            gui_max(g_win.height - HEADER_H - FOOTER_H - LABEL_H - PAD, ROW_H)};
}

static Rect zone_list_rect() {
    Rect country = country_list_rect();
    return {country.x + country.w + GAP, country.y,
            g_win.width - PAD * 2 - GAP - country.w, country.h};
}

static Rect apply_button_rect() {
    return {g_win.width - PAD - 104, g_win.height - FOOTER_H + 14, 104, 30};
}

static Rect revert_button_rect() {
    return {g_win.width - PAD - 104 - GAP - 92, g_win.height - FOOTER_H + 14, 92, 30};
}

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);
}

static bool hover_changed(const Rect& rect, bool enabled,
                          int prev_x, int prev_y, int next_x, int next_y) {
    if (!enabled) return false;
    return rect.contains(prev_x, prev_y) != rect.contains(next_x, next_y);
}

static void draw_list_frame(Canvas& canvas, const Rect& list, const mtk::Theme& theme) {
    canvas.fill_rect(list.x, list.y, list.w, list.h, theme.surface_alt);
    canvas.rect(list.x, list.y, list.w, list.h, theme.border);
}

static void draw_country_list(Canvas& canvas, const Rect& list, const mtk::Theme& theme) {
    draw_list_frame(canvas, list, theme);
    int rows = visible_rows(list);
    for (int row = 0; row < rows; row++) {
        int index = g_country_scroll + row;
        if (index >= g_country_count) break;

        Rect item = {list.x + 2, list.y + 2 + row * ROW_H, list.w - 4, ROW_H - 2};
        bool selected = index == g_selected_country;
        mtk::draw_list_row(canvas, item, selected, (index & 1) != 0, theme);

        char label[128];
        fit_text(label, sizeof(label), g_countries[index].name, item.w - 16);
        canvas.text(item.x + 8, item.y + (item.h - system_font_height()) / 2,
                    label, selected ? theme.accent_fg : theme.text);
    }
}

static void draw_zone_list(Canvas& canvas, const Rect& list, const mtk::Theme& theme) {
    draw_list_frame(canvas, list, theme);
    if (g_selected_country < 0 || g_selected_country >= g_country_count) return;

    CountryEntry& country = g_countries[g_selected_country];
    int count = zone_count(country);
    int rows = visible_rows(list);
    for (int row = 0; row < rows; row++) {
        int index = g_zone_scroll + row;
        if (index >= count) break;

        Rect item = {list.x + 2, list.y + 2 + row * ROW_H, list.w - 4, ROW_H - 2};
        bool selected = index == g_selected_zone;
        mtk::draw_list_row(canvas, item, selected, (index & 1) != 0, theme);

        char label[128];
        fit_text(label, sizeof(label), zone_name(country, index), item.w - 16);
        canvas.text(item.x + 8, item.y + (item.h - system_font_height()) / 2,
                    label, selected ? theme.accent_fg : theme.text);
    }
}

static void draw_details(Canvas& canvas, const mtk::Theme& theme) {
    if (g_country_count <= 0) return;

    Rect country = country_list_rect();
    Rect zone = zone_list_rect();
    int x = zone.x;
    int y = zone.y + zone.h + 8;
    if (y > g_win.height - FOOTER_H - 24) return;

    CountryEntry& entry = g_countries[g_selected_country];
    char line[192];
    snprintf(line, sizeof(line), "%s / %s", entry.name, zone_name(entry, g_selected_zone));
    fit_text(line, sizeof(line), line, g_win.width - x - PAD);
    canvas.text(x, y, line, theme.text);

    int offset = 0;
    char right[96];
    if (selected_offset_minutes(&offset)) {
        format_offset(offset, right, sizeof(right));
    } else {
        format_offset(montauk::gettz(), right, sizeof(right));
        char tmp[96];
        snprintf(tmp, sizeof(tmp), "Clock: %s", right);
        safe_copy(right, sizeof(right), tmp);
    }
    canvas.text(country.x, y, right, theme.text_subtle);

    y += system_font_height() + 6;
    if (entry.dst_enabled) {
        int dst_change = dst_offset_change_minutes(entry);
        if (dst_change)
            snprintf(line, sizeof(line), "DST: %s (+%d min)",
                     entry.dst_rule[0] ? entry.dst_rule : "enabled", dst_change);
        else
            snprintf(line, sizeof(line), "DST: %s",
                     entry.dst_rule[0] ? entry.dst_rule : "enabled");
    } else {
        safe_copy(line, sizeof(line), "DST: not used");
    }
    canvas.text(x, y, line, theme.text_subtle);

    if (entry.note && entry.note[0]) {
        char note[192];
        fit_text(note, sizeof(note), entry.note, g_win.width - x - PAD);
        canvas.text(x, y + system_font_height() + 4, note, theme.text_subtle);
    }
}

static void render() {
    mtk::StandaloneHost host(&g_win);
    Canvas canvas = host.canvas();
    mtk::Theme theme = app_theme();

    canvas.fill(theme.window_bg);

    if (g_country_count <= 0) {
        canvas.text(PAD, HEADER_H + 24,
                    "No time zone data was found in 0:/config/timezonedata.toml",
                    theme.danger);
        host.present();
        return;
    }

    Rect country = country_list_rect();
    Rect zone = zone_list_rect();

    canvas.text(country.x, HEADER_H + 6, "Country", theme.text_subtle);
    canvas.text(zone.x, HEADER_H + 6, "Time Zone", theme.text_subtle);

    draw_country_list(canvas, country, theme);
    draw_zone_list(canvas, zone, theme);
    draw_details(canvas, theme);

    Rect footer = {0, g_win.height - FOOTER_H, g_win.width, FOOTER_H};
    canvas.fill_rect(footer.x, footer.y, footer.w, footer.h, theme.surface);
    mtk::draw_separator(canvas, 0, footer.y, g_win.width, theme);

    const char* footer_text = status_visible()
        ? g_status
        : (g_dirty ? "Unsaved time zone selection" : "Selection saved");
    int max_footer_text = revert_button_rect().x - PAD - GAP;
    char footer_label[160];
    fit_text(footer_label, sizeof(footer_label), footer_text, max_footer_text);
    canvas.text(PAD, footer.y + (FOOTER_H - system_font_height()) / 2,
                footer_label, g_dirty ? theme.text : theme.text_subtle);

    Rect revert = revert_button_rect();
    Rect apply = apply_button_rect();
    mtk::draw_button(canvas, revert, "Revert", mtk::BUTTON_SECONDARY,
                     button_state(revert, g_dirty), theme);
    mtk::draw_button(canvas, apply, "Apply", mtk::BUTTON_PRIMARY,
                     button_state(apply, g_dirty), theme);

    host.present();
}

static bool handle_mouse(const Montauk::WinEvent& ev) {
    int prev_x = g_mouse_x;
    int prev_y = g_mouse_y;
    g_mouse_x = ev.mouse.x;
    g_mouse_y = ev.mouse.y;

    Rect country = country_list_rect();
    Rect zone = zone_list_rect();
    Rect revert = revert_button_rect();
    Rect apply = apply_button_rect();
    bool redraw = false;

    redraw |= hover_changed(revert, g_dirty, prev_x, prev_y, g_mouse_x, g_mouse_y);
    redraw |= hover_changed(apply, g_dirty, prev_x, prev_y, g_mouse_x, g_mouse_y);

    if (ev.mouse.scroll != 0) {
        if (country.contains(g_mouse_x, g_mouse_y)) {
            g_country_scroll -= ev.mouse.scroll * 3;
            clamp_scrolls();
            redraw = true;
        } else if (zone.contains(g_mouse_x, g_mouse_y)) {
            g_zone_scroll -= ev.mouse.scroll * 3;
            clamp_scrolls();
            redraw = true;
        }
    }

    bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
    if (!clicked) return redraw;

    if (country.contains(g_mouse_x, g_mouse_y)) {
        int row = (g_mouse_y - country.y - 2) / ROW_H;
        int index = g_country_scroll + row;
        if (index >= 0 && index < g_country_count) {
            g_selected_country = index;
            g_selected_zone = default_zone_index(g_countries[g_selected_country]);
            g_zone_scroll = 0;
            g_focus_pane = 0;
            g_dirty = (g_selected_country != g_saved_country || g_selected_zone != g_saved_zone);
            clamp_scrolls();
            redraw = true;
        }
        return redraw;
    }

    if (zone.contains(g_mouse_x, g_mouse_y)) {
        int row = (g_mouse_y - zone.y - 2) / ROW_H;
        int index = g_zone_scroll + row;
        if (g_selected_country >= 0 && g_selected_country < g_country_count &&
            index >= 0 && index < zone_count(g_countries[g_selected_country])) {
            g_selected_zone = index;
            g_focus_pane = 1;
            g_dirty = (g_selected_country != g_saved_country || g_selected_zone != g_saved_zone);
            redraw = true;
        }
        return redraw;
    }

    if (apply.contains(g_mouse_x, g_mouse_y)) {
        if (g_dirty) save_selection();
        return true;
    }

    if (revert.contains(g_mouse_x, g_mouse_y)) {
        if (g_dirty) revert_selection();
        return true;
    }

    return redraw;
}

static bool handle_key(const Montauk::KeyEvent& key) {
    if (!key.pressed || g_country_count <= 0) return false;

    if (key.ascii == '\n' || key.ascii == '\r') {
        if (g_dirty) save_selection();
        return true;
    }
    if (key.ascii == '\033') {
        if (g_dirty) revert_selection();
        return true;
    }

    int* selected = g_focus_pane == 0 ? &g_selected_country : &g_selected_zone;
    int count = g_focus_pane == 0
        ? g_country_count
        : zone_count(g_countries[g_selected_country]);

    int old_country = g_selected_country;

    if (key.scancode == 0x4B) g_focus_pane = 0;      // Left
    else if (key.scancode == 0x4D) g_focus_pane = 1; // Right
    else if (key.scancode == 0x48 && *selected > 0) (*selected)--; // Up
    else if (key.scancode == 0x50 && *selected < count - 1) (*selected)++; // Down
    else if (key.scancode == 0x49) *selected = gui_max(0, *selected - 8); // Page Up
    else if (key.scancode == 0x51) *selected = gui_min(count - 1, *selected + 8); // Page Down
    else return false;

    if (old_country != g_selected_country) {
        g_selected_zone = default_zone_index(g_countries[g_selected_country]);
        g_zone_scroll = 0;
    }

    ensure_country_visible();
    ensure_zone_visible();
    clamp_scrolls();

    g_dirty = (g_selected_country != g_saved_country || g_selected_zone != g_saved_zone);
    return true;
}

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

    apply_scale(montauk::win_getscale());
    load_accent();
    load_timezone_data();

    if (!g_win.create("Time Zone", INIT_W, INIT_H))
        montauk::exit(1);

    load_saved_selection();
    render();

    while (g_win.id >= 0 && !g_win.closed) {
        Montauk::WinEvent ev;
        int r = g_win.poll(&ev);
        bool redraw = false;

        if (r < 0) break;
        if (r == 0) {
            montauk::sleep_ms(16);
            continue;
        }

        if (ev.type == 3) break;
        if (ev.type == 2) {
            redraw = true;
        } else if (ev.type == 4) {
            apply_scale(g_win.scale_factor);
            redraw = true;
        } else if (ev.type == 1) {
            redraw = handle_mouse(ev);
        } else if (ev.type == 0) {
            redraw = handle_key(ev.key);
        }

        if (redraw) render();
    }

    if (g_data_loaded) g_data.destroy();
    g_win.destroy();
    montauk::exit(0);
}
