/*
 * main.cpp
 * MontaukOS Screenshot Tool
 * Captures the screen framebuffer and saves JPEG screenshots through a GUI flow.
 * Copyright (c) 2026 Daniel Hammer
 */

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

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

#define STBI_WRITE_NO_STDIO
#include <gui/stb_image_write.h>
}

using namespace gui;

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

static constexpr int INIT_W = 420;
static constexpr int INIT_H = 230;
static constexpr int PAD = 18;
static constexpr int OPTION_H = 44;
static constexpr int OPTION_GAP = 8;
static constexpr int FOOTER_H = 56;
static constexpr int BUTTON_W = 132;
static constexpr int BUTTON_H = 30;
static constexpr int MIN_AREA_SIZE = 4;

// ============================================================================
// App state
// ============================================================================

enum CaptureMode : uint8_t {
    CAPTURE_SCREEN = 0,
    CAPTURE_AREA = 1,
};

struct ScreenBuffer {
    uint32_t* pixels;
    int width;
    int height;
    int pitch;
};

struct JpegBuffer {
    uint8_t* data;
    uint64_t size;
    uint64_t capacity;
    bool failed;
};

struct AreaRenderCache {
    uint32_t* dimmed_pixels;
    int width;
    int height;
};

static int g_win_w = INIT_W;
static int g_win_h = INIT_H;
static int g_mouse_x = -1;
static int g_mouse_y = -1;
static CaptureMode g_mode = CAPTURE_SCREEN;

// ============================================================================
// Small helpers
// ============================================================================

static void safe_copy(char* dst, int dst_len, const char* src) {
    if (!dst || dst_len <= 0) return;
    if (!src) src = "";
    montauk::strncpy(dst, src, dst_len);
    dst[dst_len - 1] = '\0';
}

static bool str_ends_with_ci(const char* s, const char* suffix) {
    if (!s || !suffix) return false;
    int slen = montauk::slen(s);
    int suflen = montauk::slen(suffix);
    if (suflen > slen) return false;

    const char* tail = s + slen - suflen;
    for (int i = 0; i < suflen; i++) {
        char a = tail[i];
        char b = suffix[i];
        if (a >= 'A' && a <= 'Z') a += 32;
        if (b >= 'A' && b <= 'Z') b += 32;
        if (a != b) return false;
    }
    return true;
}

static void show_error(const char* message) {
    if (message && message[0]) {
        montauk::print("screenshot: ");
        montauk::print(message);
        montauk::print("\n");
        gui::dialogs::message_box("Screenshot", message, gui::dialogs::MESSAGE_BOX_OK);
    }
}

static void free_screen(ScreenBuffer* screen) {
    if (!screen) return;
    if (screen->pixels) montauk::mfree(screen->pixels);
    screen->pixels = nullptr;
    screen->width = 0;
    screen->height = 0;
    screen->pitch = 0;
}

static Rect normalized_rect(int x0, int y0, int x1, int y1) {
    int left = x0 < x1 ? x0 : x1;
    int top = y0 < y1 ? y0 : y1;
    int right = x0 > x1 ? x0 : x1;
    int bottom = y0 > y1 ? y0 : y1;
    return {left, top, right - left + 1, bottom - top + 1};
}

static Rect clamp_rect_to_screen(const Rect& rect, int w, int h) {
    Rect bounds = {0, 0, w, h};
    return rect.intersect(bounds);
}

// ============================================================================
// MTK picker window
// ============================================================================

static mtk::Theme app_theme() {
    mtk::Theme theme = mtk::make_theme();
    theme.window_bg = colors::WHITE;
    theme.surface = Color::from_rgb(0xF5, 0xF5, 0xF5);
    theme.surface_alt = Color::from_rgb(0xFA, 0xFA, 0xFA);
    theme.surface_hover = mtk::mix(theme.surface, theme.accent, 18);
    theme.border = Color::from_rgb(0xD0, 0xD0, 0xD0);
    theme.text = Color::from_rgb(0x2E, 0x2E, 0x2E);
    theme.text_muted = Color::from_rgb(0x74, 0x74, 0x74);
    theme.text_subtle = Color::from_rgb(0x88, 0x88, 0x88);
    return theme;
}

static Rect option_rect(int index) {
    int top = 48 + index * (OPTION_H + OPTION_GAP);
    return {PAD, top, gui_max(g_win_w - PAD * 2, 0), OPTION_H};
}

static Rect capture_button_rect() {
    int y = g_win_h - FOOTER_H + (FOOTER_H - BUTTON_H) / 2;
    int w = gui_min(BUTTON_W, gui_max((g_win_w - PAD * 3) / 2, 72));
    return {g_win_w - PAD - w, y, w, BUTTON_H};
}

static Rect cancel_button_rect() {
    Rect capture = capture_button_rect();
    int w = capture.w;
    return {capture.x - 8 - w, capture.y, w, BUTTON_H};
}

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

static void draw_option(Canvas& c,
                        const Rect& rect,
                        CaptureMode mode,
                        const char* label,
                        const mtk::Theme& theme) {
    if (rect.empty()) return;

    bool selected = g_mode == mode;
    bool hovered = mouse_in_rect(rect);
    if (selected) {
        Color fill = hovered ? mtk::mix(theme.accent_soft, theme.accent, 14)
                             : theme.accent_soft;
        mtk::draw_rounded_frame(c, rect, theme.radius_md, fill, fill);
    } else if (hovered) {
        mtk::draw_rounded_frame(c, rect, theme.radius_md,
                                theme.surface_hover, theme.surface_hover);
    }

    Rect radio = {rect.x + 12, rect.y + (rect.h - 24) / 2, rect.w - 24, 24};
    mtk::draw_radio(c, radio, label, selected, theme);
}

static void render_picker(Canvas& c) {
    mtk::Theme theme = app_theme();
    c.fill(theme.window_bg);

    c.text(PAD, 16, "Capture mode", theme.text);

    draw_option(c, option_rect(0), CAPTURE_SCREEN, "Whole screen", theme);
    draw_option(c, option_rect(1), CAPTURE_AREA, "Selected area", theme);

    int footer_y = gui_max(g_win_h - FOOTER_H, 0);
    c.fill_rect(0, footer_y, g_win_w, gui_max(g_win_h - footer_y, 0), theme.surface);
    mtk::draw_separator(c, 0, footer_y, g_win_w, theme);

    Rect cancel = cancel_button_rect();
    Rect capture = capture_button_rect();
    mtk::draw_button(c, cancel, "Cancel", mtk::BUTTON_SECONDARY,
                     mtk::widget_state(false, mouse_in_rect(cancel)), theme);
    mtk::draw_button(c, capture, "Capture", mtk::BUTTON_PRIMARY,
                     mtk::widget_state(false, mouse_in_rect(capture)), theme);
}

static bool run_picker(CaptureMode* out_mode) {
    if (!fonts::init()) {
        show_error("failed to initialize fonts");
        return false;
    }

    WsWindow win;
    if (!win.create("Screenshot", INIT_W, INIT_H)) {
        show_error("failed to create window");
        return false;
    }

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

    bool accepted = false;
    while (true) {
        Montauk::WinEvent ev;
        int r = win.poll(&ev);

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

        bool redraw = false;
        if (ev.type == 3) {
            break;
        } else if (ev.type == 2) {
            g_win_w = win.width;
            g_win_h = win.height;
            redraw = true;
        } else if (ev.type == 4) {
            redraw = true;
        } else if (ev.type == 0 && ev.key.pressed) {
            if (ev.key.scancode == 0x01) break;
            if (ev.key.ascii == '\n' || ev.key.ascii == '\r') {
                accepted = true;
                break;
            }
            if (ev.key.ascii == '1') {
                g_mode = CAPTURE_SCREEN;
                redraw = true;
            } else if (ev.key.ascii == '2') {
                g_mode = CAPTURE_AREA;
                redraw = true;
            }
        } else if (ev.type == 1) {
            g_mouse_x = ev.mouse.x;
            g_mouse_y = ev.mouse.y;
            redraw = true;

            bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
            if (clicked) {
                if (option_rect(0).contains(ev.mouse.x, ev.mouse.y)) {
                    g_mode = CAPTURE_SCREEN;
                } else if (option_rect(1).contains(ev.mouse.x, ev.mouse.y)) {
                    g_mode = CAPTURE_AREA;
                } else if (cancel_button_rect().contains(ev.mouse.x, ev.mouse.y)) {
                    break;
                } else if (capture_button_rect().contains(ev.mouse.x, ev.mouse.y)) {
                    accepted = true;
                    break;
                }
            }
        }

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

    if (out_mode) *out_mode = g_mode;
    win.destroy();
    return accepted;
}

// ============================================================================
// Framebuffer capture
// ============================================================================

static bool capture_screen(ScreenBuffer* out, char* err, int err_len) {
    if (!out) return false;
    out->pixels = nullptr;
    out->width = 0;
    out->height = 0;
    out->pitch = 0;

    Montauk::FbInfo fb;
    montauk::fb_info(&fb);

    int width = (int)fb.width;
    int height = (int)fb.height;
    int pitch = (int)fb.pitch;
    if (width <= 0 || height <= 0 || pitch <= 0) {
        safe_copy(err, err_len, "no framebuffer available");
        return false;
    }

    uint32_t* hw_fb = (uint32_t*)montauk::fb_map();
    if (!hw_fb) {
        safe_copy(err, err_len, "failed to map framebuffer");
        return false;
    }

    uint64_t size = (uint64_t)width * (uint64_t)height * sizeof(uint32_t);
    uint32_t* pixels = (uint32_t*)montauk::malloc(size);
    if (!pixels) {
        safe_copy(err, err_len, "out of memory");
        return false;
    }

    for (int y = 0; y < height; y++) {
        uint32_t* src = (uint32_t*)((uint8_t*)hw_fb + y * pitch);
        montauk::memcpy(pixels + y * width, src, (uint64_t)width * sizeof(uint32_t));
    }

    out->pixels = pixels;
    out->width = width;
    out->height = height;
    out->pitch = pitch;
    return true;
}

static uint32_t dim_pixel(uint32_t px) {
    uint8_t r = (uint8_t)((px >> 16) & 0xFF);
    uint8_t g = (uint8_t)((px >> 8) & 0xFF);
    uint8_t b = (uint8_t)(px & 0xFF);
    r = (uint8_t)((uint16_t)r * 3 / 5);
    g = (uint8_t)((uint16_t)g * 3 / 5);
    b = (uint8_t)((uint16_t)b * 3 / 5);
    return (px & 0xFF000000u) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}

static void free_area_render_cache(AreaRenderCache* cache) {
    if (!cache) return;
    if (cache->dimmed_pixels)
        montauk::mfree(cache->dimmed_pixels);
    cache->dimmed_pixels = nullptr;
    cache->width = 0;
    cache->height = 0;
}

static void build_area_render_cache(const ScreenBuffer& screen, AreaRenderCache* cache) {
    if (!cache || !screen.pixels || screen.width <= 0 || screen.height <= 0)
        return;

    uint64_t count = (uint64_t)screen.width * (uint64_t)screen.height;
    uint64_t bytes = count * sizeof(uint32_t);
    uint32_t* dimmed = (uint32_t*)montauk::malloc(bytes);
    if (!dimmed)
        return;

    for (uint64_t i = 0; i < count; i++)
        dimmed[i] = dim_pixel(screen.pixels[i]);

    free_area_render_cache(cache);
    cache->dimmed_pixels = dimmed;
    cache->width = screen.width;
    cache->height = screen.height;
}

// ============================================================================
// Area selection window
// ============================================================================

struct AreaSelectorState {
    int win_w;
    int win_h;
    int mouse_x;
    int mouse_y;
    bool dragging;
    bool has_selection;
    bool fullscreen;
    int drag_start_x;
    int drag_start_y;
    Rect selection_view;
};

static int area_initial_w(const ScreenBuffer& screen) {
    int w = screen.width - 120;
    return gui_clamp(w, 360, 760);
}

static int area_initial_h(const ScreenBuffer& screen) {
    int h = screen.height - 140;
    return gui_clamp(h, 280, 560);
}

static Rect area_footer_rect(const AreaSelectorState& st) {
    return {0, st.win_h - FOOTER_H, st.win_w, FOOTER_H};
}

static Rect area_cancel_button_rect(const AreaSelectorState& st) {
    Rect footer = area_footer_rect(st);
    int w = gui_min(BUTTON_W, gui_max((st.win_w - PAD * 3) / 2, 72));
    int y = footer.y + (footer.h - BUTTON_H) / 2;
    return {st.win_w - PAD - w * 2 - 8, y, w, BUTTON_H};
}

static Rect area_capture_button_rect(const AreaSelectorState& st) {
    Rect footer = area_footer_rect(st);
    int w = gui_min(BUTTON_W, gui_max((st.win_w - PAD * 3) / 2, 72));
    int y = footer.y + (footer.h - BUTTON_H) / 2;
    return {st.win_w - PAD - w, y, w, BUTTON_H};
}

static Rect area_preview_rect(const ScreenBuffer& screen, const AreaSelectorState& st) {
    if (st.fullscreen) {
        return {0, 0, gui_max(st.win_w, 1), gui_max(st.win_h, 1)};
    }

    Rect content = {PAD, PAD,
                    gui_max(st.win_w - PAD * 2, 1),
                    gui_max(st.win_h - FOOTER_H - PAD * 2, 1)};
    if (screen.width <= 0 || screen.height <= 0) return content;

    int preview_w = content.w;
    int preview_h = (int)((uint64_t)preview_w * (uint64_t)screen.height / (uint64_t)screen.width);
    if (preview_h > content.h) {
        preview_h = content.h;
        preview_w = (int)((uint64_t)preview_h * (uint64_t)screen.width / (uint64_t)screen.height);
    }
    if (preview_w < 1) preview_w = 1;
    if (preview_h < 1) preview_h = 1;

    return {content.x + (content.w - preview_w) / 2,
            content.y + (content.h - preview_h) / 2,
            preview_w,
            preview_h};
}

static bool area_mouse_in(const AreaSelectorState& st, const Rect& rect) {
    return rect.contains(st.mouse_x, st.mouse_y);
}

static Rect clamp_rect_to_preview(const Rect& rect, const Rect& preview) {
    return rect.intersect(preview);
}

static bool draw_fullscreen_preview(Canvas& c,
                                    const ScreenBuffer& screen,
                                    const Rect& preview,
                                    const Rect& selection,
                                    bool has_selection,
                                    const AreaRenderCache* cache) {
    if (!screen.pixels || preview.x != 0 || preview.y != 0 ||
        preview.w != c.w || preview.h != c.h ||
        screen.width != c.w || screen.height != c.h) {
        return false;
    }

    uint64_t bytes = (uint64_t)c.w * (uint64_t)c.h * sizeof(uint32_t);
    Rect sel = clamp_rect_to_preview(selection, preview);
    bool draw_dimmed = has_selection && !sel.empty() &&
                       cache && cache->dimmed_pixels &&
                       cache->width == screen.width &&
                       cache->height == screen.height;

    if (draw_dimmed)
        montauk::memcpy(c.pixels, cache->dimmed_pixels, bytes);
    else
        montauk::memcpy(c.pixels, screen.pixels, bytes);

    if (draw_dimmed) {
        for (int y = 0; y < sel.h; y++) {
            uint32_t* dst = c.pixels + (sel.y + y) * c.w + sel.x;
            uint32_t* src = screen.pixels + (sel.y + y) * screen.width + sel.x;
            montauk::memcpy(dst, src, (uint64_t)sel.w * sizeof(uint32_t));
        }
    }

    return true;
}

static void draw_scaled_preview(Canvas& c,
                                const ScreenBuffer& screen,
                                const Rect& preview,
                                const Rect& selection,
                                bool has_selection,
                                const AreaRenderCache* cache) {
    if (!screen.pixels || preview.empty()) return;
    if (draw_fullscreen_preview(c, screen, preview, selection, has_selection, cache))
        return;

    Rect sel = clamp_rect_to_preview(selection, preview);
    for (int y = 0; y < preview.h; y++) {
        int dst_y = preview.y + y;
        if (dst_y < 0 || dst_y >= c.h) continue;
        int src_y = (int)((uint64_t)y * (uint64_t)screen.height / (uint64_t)preview.h);
        uint32_t* src_row = screen.pixels + src_y * screen.width;
        uint32_t* dst_row = c.pixels + dst_y * c.w;

        bool y_in_sel = has_selection && dst_y >= sel.y && dst_y < sel.y + sel.h;
        for (int x = 0; x < preview.w; x++) {
            int dst_x = preview.x + x;
            if (dst_x < 0 || dst_x >= c.w) continue;
            int src_x = (int)((uint64_t)x * (uint64_t)screen.width / (uint64_t)preview.w);
            uint32_t px = src_row[src_x];
            bool in_sel = y_in_sel && dst_x >= sel.x && dst_x < sel.x + sel.w;
            dst_row[dst_x] = (!has_selection || in_sel) ? px : dim_pixel(px);
        }
    }
}

static void draw_area_selector(Canvas& c,
                               const ScreenBuffer& screen,
                               const AreaSelectorState& st,
                               const AreaRenderCache* cache) {
    mtk::Theme theme = app_theme();
    if (!st.fullscreen)
        c.fill(theme.window_bg);

    Rect preview = area_preview_rect(screen, st);
    if (!st.fullscreen)
        c.fill_rect(preview.x - 1, preview.y - 1, preview.w + 2, preview.h + 2, theme.border);
    draw_scaled_preview(c, screen, preview, st.selection_view, st.has_selection || st.dragging, cache);

    if ((st.has_selection || st.dragging) && !st.selection_view.empty()) {
        Rect sel = clamp_rect_to_preview(st.selection_view, preview);
        if (!sel.empty()) {
            c.rect(sel.x, sel.y, sel.w, sel.h, colors::WHITE);
            if (sel.w > 2 && sel.h > 2) {
                Rect inner = {sel.x + 1, sel.y + 1, sel.w - 2, sel.h - 2};
                c.rect(inner.x, inner.y, inner.w, inner.h, theme.accent);
            }
        }
    }

    if (st.fullscreen)
        return;

    Rect footer = area_footer_rect(st);
    c.fill_rect(footer.x, footer.y, footer.w, footer.h, theme.surface);
    mtk::draw_separator(c, 0, footer.y, st.win_w, theme);

    if (st.has_selection) {
        Rect screen_sel = {0, 0, 0, 0};
        Rect sel = clamp_rect_to_preview(st.selection_view, preview);
        if (!sel.empty()) {
            int left = (int)((uint64_t)(sel.x - preview.x) * (uint64_t)screen.width / (uint64_t)preview.w);
            int top = (int)((uint64_t)(sel.y - preview.y) * (uint64_t)screen.height / (uint64_t)preview.h);
            int right = (int)((uint64_t)(sel.x + sel.w - preview.x) * (uint64_t)screen.width / (uint64_t)preview.w);
            int bottom = (int)((uint64_t)(sel.y + sel.h - preview.y) * (uint64_t)screen.height / (uint64_t)preview.h);
            screen_sel = clamp_rect_to_screen({left, top, right - left, bottom - top},
                                              screen.width, screen.height);
        }

        char size_label[48];
        snprintf(size_label, sizeof(size_label), "%d x %d", screen_sel.w, screen_sel.h);
        c.text(PAD, footer.y + (footer.h - system_font_height()) / 2,
               size_label, theme.text_muted);
    }

    Rect cancel = area_cancel_button_rect(st);
    Rect capture = area_capture_button_rect(st);
    mtk::draw_button(c, cancel, "Cancel", mtk::BUTTON_SECONDARY,
                     mtk::widget_state(false, area_mouse_in(st, cancel)), theme);
    mtk::draw_button(c, capture, "Capture", mtk::BUTTON_PRIMARY,
                     mtk::widget_state(false, area_mouse_in(st, capture), st.has_selection), theme);
}

static Point clamp_point_to_rect(int x, int y, const Rect& rect) {
    return {gui_clamp(x, rect.x, rect.x + rect.w - 1),
            gui_clamp(y, rect.y, rect.y + rect.h - 1)};
}

static Rect view_selection_to_screen(const ScreenBuffer& screen,
                                     const Rect& preview,
                                     const Rect& selection) {
    Rect sel = clamp_rect_to_preview(selection, preview);
    if (sel.empty()) return {0, 0, 0, 0};

    int left = (int)((uint64_t)(sel.x - preview.x) * (uint64_t)screen.width / (uint64_t)preview.w);
    int top = (int)((uint64_t)(sel.y - preview.y) * (uint64_t)screen.height / (uint64_t)preview.h);
    int right = (int)((uint64_t)(sel.x + sel.w - preview.x) * (uint64_t)screen.width / (uint64_t)preview.w);
    int bottom = (int)((uint64_t)(sel.y + sel.h - preview.y) * (uint64_t)screen.height / (uint64_t)preview.h);
    if (right <= left) right = left + 1;
    if (bottom <= top) bottom = top + 1;

    return clamp_rect_to_screen({left, top, right - left, bottom - top},
                                screen.width, screen.height);
}

enum AreaEventResult : uint8_t {
    AREA_EVENT_CONTINUE = 0,
    AREA_EVENT_ACCEPT,
    AREA_EVENT_CANCEL,
};

static AreaEventResult handle_area_selector_event(WsWindow& win,
                                                  const ScreenBuffer& screen,
                                                  AreaSelectorState& st,
                                                  const Montauk::WinEvent& ev,
                                                  bool* redraw) {
    if (!redraw) return AREA_EVENT_CONTINUE;

    if (ev.type == 3) {
        return AREA_EVENT_CANCEL;
    }

    if (ev.type == 2) {
        st.win_w = win.width;
        st.win_h = win.height;
        st.selection_view = clamp_rect_to_preview(st.selection_view,
                                                  area_preview_rect(screen, st));
        *redraw = true;
        return AREA_EVENT_CONTINUE;
    }

    if (ev.type == 4) {
        *redraw = true;
        return AREA_EVENT_CONTINUE;
    }

    if (ev.type == 0 && ev.key.pressed) {
        if (ev.key.scancode == 0x01)
            return AREA_EVENT_CANCEL;
        if ((ev.key.ascii == '\n' || ev.key.ascii == '\r') && st.has_selection)
            return AREA_EVENT_ACCEPT;
        return AREA_EVENT_CONTINUE;
    }

    if (ev.type != 1)
        return AREA_EVENT_CONTINUE;

    int old_x = st.mouse_x;
    int old_y = st.mouse_y;
    st.mouse_x = ev.mouse.x;
    st.mouse_y = ev.mouse.y;
    if (old_x != st.mouse_x || old_y != st.mouse_y)
        *redraw = true;

    Rect preview = area_preview_rect(screen, st);
    bool left_pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
    bool left_held = (ev.mouse.buttons & 1) != 0;
    bool left_released = !(ev.mouse.buttons & 1) && (ev.mouse.prev_buttons & 1);

    if (left_pressed) {
        if (!st.fullscreen) {
            Rect cancel = area_cancel_button_rect(st);
            Rect capture = area_capture_button_rect(st);
            if (cancel.contains(ev.mouse.x, ev.mouse.y))
                return AREA_EVENT_CANCEL;
            if (capture.contains(ev.mouse.x, ev.mouse.y) && st.has_selection)
                return AREA_EVENT_ACCEPT;
        }
        if (preview.contains(ev.mouse.x, ev.mouse.y)) {
            Point p = clamp_point_to_rect(ev.mouse.x, ev.mouse.y, preview);
            st.dragging = true;
            st.has_selection = false;
            st.drag_start_x = p.x;
            st.drag_start_y = p.y;
            st.selection_view = {p.x, p.y, 1, 1};
            *redraw = true;
        }
    } else if (st.dragging && left_held) {
        Point p = clamp_point_to_rect(ev.mouse.x, ev.mouse.y, preview);
        st.selection_view = clamp_rect_to_preview(
            normalized_rect(st.drag_start_x, st.drag_start_y, p.x, p.y),
            preview);
        *redraw = true;
    } else if (st.dragging && left_released) {
        Point p = clamp_point_to_rect(ev.mouse.x, ev.mouse.y, preview);
        st.selection_view = clamp_rect_to_preview(
            normalized_rect(st.drag_start_x, st.drag_start_y, p.x, p.y),
            preview);
        st.dragging = false;
        st.has_selection = st.selection_view.w >= MIN_AREA_SIZE
                        && st.selection_view.h >= MIN_AREA_SIZE;
        if (st.fullscreen && st.has_selection)
            return AREA_EVENT_ACCEPT;
        *redraw = true;
    }

    return AREA_EVENT_CONTINUE;
}

static bool run_area_selector(const ScreenBuffer& screen, Rect* out_area) {
    if (!screen.pixels || !out_area) return false;

    AreaSelectorState st = {};
    st.fullscreen = true;
    st.win_w = st.fullscreen ? screen.width : area_initial_w(screen);
    st.win_h = st.fullscreen ? screen.height : area_initial_h(screen);
    st.mouse_x = -1;
    st.mouse_y = -1;

    WsWindow win;
    if (!win.create("Select Area", st.win_w, st.win_h)) {
        show_error("failed to create area selection window");
        return false;
    }
    if (st.fullscreen)
        win.set_flags(Montauk::WIN_FLAG_FULLSCREEN);

    mtk::StandaloneHost host(&win);
    AreaRenderCache cache = {};
    Canvas canvas = host.canvas();
    draw_area_selector(canvas, screen, st, &cache);
    host.present();
    if (st.fullscreen)
        build_area_render_cache(screen, &cache);

    bool accepted = false;
    while (true) {
        Montauk::WinEvent ev;
        int r = win.poll(&ev);

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

        bool redraw = false;
        AreaEventResult result = handle_area_selector_event(win, screen, st, ev, &redraw);

        for (int drain = 0; result == AREA_EVENT_CONTINUE && drain < 64; drain++) {
            Montauk::WinEvent queued;
            int qr = win.poll(&queued);
            if (qr < 0) {
                result = AREA_EVENT_CANCEL;
                break;
            }
            if (qr == 0)
                break;
            result = handle_area_selector_event(win, screen, st, queued, &redraw);
        }

        if (result == AREA_EVENT_ACCEPT) {
            accepted = true;
            break;
        }
        if (result == AREA_EVENT_CANCEL)
            break;

        if (redraw) {
            canvas = host.canvas();
            draw_area_selector(canvas, screen, st, &cache);
            host.present();
        }
    }

    if (accepted) {
        Rect preview = area_preview_rect(screen, st);
        *out_area = view_selection_to_screen(screen, preview, st.selection_view);
        if (out_area->empty())
            accepted = false;
    }

    win.destroy();
    free_area_render_cache(&cache);
    return accepted;
}

// ============================================================================
// JPEG encoding and saving
// ============================================================================

static void jpeg_write_callback(void* context, void* data, int size) {
    JpegBuffer* buf = (JpegBuffer*)context;
    if (!buf || size <= 0 || buf->failed) return;

    uint64_t needed = buf->size + (uint64_t)size;
    if (needed > buf->capacity) {
        uint64_t new_cap = buf->capacity * 2;
        if (new_cap < needed) new_cap = needed;
        uint8_t* new_data = (uint8_t*)montauk::realloc(buf->data, new_cap);
        if (!new_data) {
            buf->failed = true;
            return;
        }
        buf->data = new_data;
        buf->capacity = new_cap;
    }
    montauk::memcpy(buf->data + buf->size, data, size);
    buf->size += (uint64_t)size;
}

static bool encode_jpeg(const ScreenBuffer& screen,
                        const Rect& area,
                        JpegBuffer* out,
                        char* err,
                        int err_len) {
    if (!screen.pixels || !out) {
        safe_copy(err, err_len, "nothing to encode");
        return false;
    }

    Rect rect = clamp_rect_to_screen(area, screen.width, screen.height);
    if (rect.empty()) {
        safe_copy(err, err_len, "empty screenshot area");
        return false;
    }

    uint64_t rgb_size = (uint64_t)rect.w * (uint64_t)rect.h * 3;
    uint8_t* rgb = (uint8_t*)montauk::malloc(rgb_size);
    if (!rgb) {
        safe_copy(err, err_len, "out of memory");
        return false;
    }

    for (int y = 0; y < rect.h; y++) {
        uint32_t* src_row = screen.pixels + (rect.y + y) * screen.width + rect.x;
        uint8_t* dst_row = rgb + (uint64_t)y * rect.w * 3;
        for (int x = 0; x < rect.w; x++) {
            uint32_t px = src_row[x];
            dst_row[x * 3 + 0] = (uint8_t)((px >> 16) & 0xFF);
            dst_row[x * 3 + 1] = (uint8_t)((px >> 8) & 0xFF);
            dst_row[x * 3 + 2] = (uint8_t)(px & 0xFF);
        }
    }

    out->data = (uint8_t*)montauk::malloc(256 * 1024);
    out->size = 0;
    out->capacity = 256 * 1024;
    out->failed = false;
    if (!out->data) {
        montauk::mfree(rgb);
        safe_copy(err, err_len, "out of memory for JPEG buffer");
        return false;
    }

    int ok = stbi_write_jpg_to_func(jpeg_write_callback, out,
                                    rect.w, rect.h, 3, rgb, 100);
    montauk::mfree(rgb);

    if (!ok || out->failed || out->size == 0) {
        if (out->data) montauk::mfree(out->data);
        out->data = nullptr;
        out->size = 0;
        out->capacity = 0;
        safe_copy(err, err_len, "JPEG encoding failed");
        return false;
    }

    return true;
}

static void build_default_save_dir(char* out, int out_len) {
    char username[32] = {};
    if (montauk::getuser(username, sizeof(username)) > 0 && username[0]) {
        char home[128];
        snprintf(home, sizeof(home), "0:/users/%s", username);
        snprintf(out, out_len, "%s/Pictures", home);
        montauk::fmkdir(out);
        return;
    }
    safe_copy(out, out_len, "0:/");
}

static void build_suggested_name(char* out, int out_len) {
    Montauk::DateTime dt;
    montauk::gettime(&dt);
    snprintf(out, out_len,
             "screenshot_%04d%02d%02d_%02d%02d%02d.jpg",
             (int)dt.Year, (int)dt.Month, (int)dt.Day,
             (int)dt.Hour, (int)dt.Minute, (int)dt.Second);
}

static bool write_jpeg_file(const char* path, const JpegBuffer& jpeg, char* err, int err_len) {
    if (!path || !path[0]) {
        safe_copy(err, err_len, "no destination selected");
        return false;
    }
    if (!jpeg.data || jpeg.size == 0) {
        safe_copy(err, err_len, "no screenshot data to save");
        return false;
    }

    int fd = montauk::fcreate(path);
    if (fd < 0) {
        safe_copy(err, err_len, "failed to create file");
        return false;
    }

    int wrote = montauk::fwrite(fd, jpeg.data, 0, jpeg.size);
    montauk::close(fd);
    if (wrote < 0 || (uint64_t)wrote != jpeg.size) {
        safe_copy(err, err_len, "failed to write file");
        return false;
    }
    return true;
}

static bool save_with_dialog(const JpegBuffer& jpeg) {
    char save_dir[192];
    char suggested[128];
    char path[256];
    char message[160];
    char err[160];

    build_default_save_dir(save_dir, sizeof(save_dir));
    build_suggested_name(suggested, sizeof(suggested));
    path[0] = '\0';
    message[0] = '\0';
    err[0] = '\0';

    bool ok = gui::dialogs::save_file("Save Screenshot", save_dir, suggested,
                                      path, sizeof(path), message, sizeof(message));
    if (!ok) {
        if (message[0]) show_error(message);
        return false;
    }

    if (!str_ends_with_ci(path, ".jpg") && !str_ends_with_ci(path, ".jpeg")) {
        int len = montauk::slen(path);
        if (len + 4 < (int)sizeof(path)) {
            path[len + 0] = '.';
            path[len + 1] = 'j';
            path[len + 2] = 'p';
            path[len + 3] = 'g';
            path[len + 4] = '\0';
        }
    }

    if (!write_jpeg_file(path, jpeg, err, sizeof(err))) {
        show_error(err);
        return false;
    }

    char msg[320];
    snprintf(msg, sizeof(msg), "Screenshot saved: %s (%llu bytes)\n",
             path, (unsigned long long)jpeg.size);
    montauk::print(msg);
    return true;
}

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

extern "C" void _start() {
    CaptureMode mode = CAPTURE_SCREEN;
    if (!run_picker(&mode))
        montauk::exit(0);

    // Let the desktop remove the picker window before reading the screen.
    montauk::sleep_ms(140);

    ScreenBuffer screen = {};
    char err[160];
    err[0] = '\0';
    if (!capture_screen(&screen, err, sizeof(err))) {
        show_error(err);
        montauk::exit(1);
    }

    Rect area = {0, 0, screen.width, screen.height};
    if (mode == CAPTURE_AREA) {
        if (!run_area_selector(screen, &area)) {
            free_screen(&screen);
            montauk::exit(0);
        }
        // Let the desktop remove the selector window before opening the save dialog.
        montauk::sleep_ms(80);
    }

    JpegBuffer jpeg = {};
    if (!encode_jpeg(screen, area, &jpeg, err, sizeof(err))) {
        free_screen(&screen);
        show_error(err);
        montauk::exit(1);
    }
    free_screen(&screen);

    save_with_dialog(jpeg);
    if (jpeg.data) montauk::mfree(jpeg.data);
    montauk::exit(0);
}
