#include "platform/platform.h"

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>

typedef struct PlatformWindow {
    HWND    hwnd;
    B32    should_close;
    S32 width;
    S32 height;
    S32 pending_menu_cmd;
    PlatformFrameCallback frame_callback;
    void                 *frame_callback_user_data;
    PlatformInput         input;
    B32                   prev_mouse_down;
} PlatformWindow;

// Main window receives menu commands
static PlatformWindow *g_main_window = NULL;
static HCURSOR g_current_cursor = NULL;
static B32 g_wndclass_registered = false;

static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    PlatformWindow *pw = (PlatformWindow *)GetWindowLongPtr(hwnd, GWLP_USERDATA);

    switch (msg) {
    case WM_SIZE:
        if (pw && wparam != SIZE_MINIMIZED) {
            pw->width  = (S32)LOWORD(lparam);
            pw->height = (S32)HIWORD(lparam);
            // Render a frame during the modal resize loop so the UI
            // stays responsive instead of showing a stretched image.
            if (pw->frame_callback) {
                pw->frame_callback(pw->frame_callback_user_data);
            }
        }
        return 0;
    case WM_CHAR:
        if (pw && wparam >= 32 && wparam < 0xFFFF) {
            PlatformInput *ev = &pw->input;
            if (ev->char_count < PLATFORM_MAX_CHARS_PER_FRAME)
                ev->chars[ev->char_count++] = (U16)wparam;
        }
        return 0;
    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
        if (pw) {
            PlatformInput *ev = &pw->input;
            if (ev->key_count < PLATFORM_MAX_KEYS_PER_FRAME)
                ev->keys[ev->key_count++] = (U8)wparam;
            ev->ctrl_held  = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
            ev->shift_held = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
        }
        break; // fall through to DefWindowProc for system keys
    case WM_MOUSEWHEEL:
        if (pw) {
            S16 wheel_delta = (S16)HIWORD(wparam);
            pw->input.scroll_delta.y += (F32)wheel_delta / (F32)WHEEL_DELTA * 6.0f;
        }
        return 0;
    case WM_COMMAND:
        // Route menu commands to main window
        if (g_main_window && HIWORD(wparam) == 0)
            g_main_window->pending_menu_cmd = (S32)LOWORD(wparam);
        return 0;
    case WM_SETCURSOR:
        // When the cursor is in our client area, use the app-set cursor.
        if (LOWORD(lparam) == HTCLIENT) {
            SetCursor(g_current_cursor ? g_current_cursor : LoadCursor(NULL, IDC_ARROW));
            return TRUE;
        }
        break;
    case WM_DPICHANGED:
        if (pw) {
            RECT *suggested = (RECT *)lparam;
            SetWindowPos(hwnd, NULL, suggested->left, suggested->top,
                suggested->right - suggested->left, suggested->bottom - suggested->top,
                SWP_NOZORDER | SWP_NOACTIVATE);
        }
        return 0;
    case WM_CLOSE:
        if (pw)
            pw->should_close = true;
        return 0;
    case WM_DESTROY:
        if (pw == g_main_window)
            PostQuitMessage(0);
        return 0;
    case WM_SYSCOMMAND:
        if ((wparam & 0xfff0) == SC_KEYMENU)
            return 0;
        break;
    }
    return DefWindowProcW(hwnd, msg, wparam, lparam);
}

PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

    if (!g_wndclass_registered) {
        WNDCLASSEXW wc = {0};
        wc.cbSize        = sizeof(wc);
        wc.style         = CS_CLASSDC;
        wc.lpfnWndProc   = win32_wndproc;
        wc.hInstance     = GetModuleHandleW(NULL);
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.lpszClassName = L"autosample_wc";
        RegisterClassExW(&wc);
        g_wndclass_registered = true;
    }

    UINT dpi = GetDpiForSystem();
    int screen_w = GetSystemMetrics(SM_CXSCREEN);
    int screen_h = GetSystemMetrics(SM_CYSCREEN);
    int x = (screen_w - desc->width) / 2;
    int y = (screen_h - desc->height) / 2;

    DWORD style;
    HWND parent_hwnd = NULL;
    if (desc->style == PLATFORM_WINDOW_STYLE_POPUP) {
        style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
        if (desc->parent && !desc->independent) parent_hwnd = desc->parent->hwnd;
    } else if (desc->style == PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE) {
        style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZEBOX;
        if (desc->parent && !desc->independent) parent_hwnd = desc->parent->hwnd;
    } else {
        style = WS_OVERLAPPEDWINDOW;
    }

    RECT rect = { 0, 0, (LONG)desc->width, (LONG)desc->height };
    AdjustWindowRectExForDpi(&rect, style, FALSE, 0, dpi);

    int wchar_count = MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, NULL, 0);
    wchar_t *wtitle = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, wtitle, wchar_count);

    HWND hwnd = CreateWindowExW(
        0, L"autosample_wc", wtitle,
        style,
        x, y,
        rect.right - rect.left,
        rect.bottom - rect.top,
        parent_hwnd, NULL, GetModuleHandleW(NULL), NULL
    );

    _freea(wtitle);

    if (!hwnd)
        return NULL;

    PlatformWindow *window = (PlatformWindow *)calloc(1, sizeof(PlatformWindow));
    window->hwnd         = hwnd;
    window->should_close = false;
    window->width        = desc->width;
    window->height       = desc->height;

    // Store PlatformWindow* on the HWND so WndProc can find it
    SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)window);

    // Track main window for menu commands
    if (desc->style == PLATFORM_WINDOW_STYLE_NORMAL) {
        g_main_window = window;
    }

    ShowWindow(hwnd, SW_SHOWDEFAULT);
    UpdateWindow(hwnd);

    return window;
}

void platform_destroy_window(PlatformWindow *window) {
    if (!window) return;

    if (window->hwnd) {
        DestroyWindow(window->hwnd);
    }

    if (g_main_window == window)
        g_main_window = NULL;

    free(window);
}

B32 platform_poll_events(PlatformWindow *window) {
    MSG msg;
    while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
        if (msg.message == WM_QUIT) {
            window->should_close = true;
        }
    }
    return !window->should_close;
}

void platform_get_size(PlatformWindow *window, S32 *w, S32 *h) {
    if (w) *w = window->width;
    if (h) *h = window->height;
}

void *platform_get_native_handle(PlatformWindow *window) {
    return (void *)window->hwnd;
}

void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data) {
    window->frame_callback = cb;
    window->frame_callback_user_data = user_data;
}

void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_count) {
    HMENU menu_bar = CreateMenu();

    for (S32 i = 0; i < menu_count; i++) {
        HMENU submenu = CreatePopupMenu();

        for (S32 j = 0; j < menus[i].item_count; j++) {
            PlatformMenuItem *item = &menus[i].items[j];
            if (!item->label) {
                AppendMenuW(submenu, MF_SEPARATOR, 0, NULL);
            } else {
                int wchar_count = MultiByteToWideChar(CP_UTF8, 0, item->label, -1, NULL, 0);
                wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
                MultiByteToWideChar(CP_UTF8, 0, item->label, -1, wlabel, wchar_count);
                AppendMenuW(submenu, MF_STRING, (UINT_PTR)item->id, wlabel);
                _freea(wlabel);
            }
        }

        int wchar_count = MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, NULL, 0);
        wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
        MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, wlabel, wchar_count);
        AppendMenuW(menu_bar, MF_POPUP, (UINT_PTR)submenu, wlabel);
        _freea(wlabel);
    }

    SetMenu(window->hwnd, menu_bar);
}

S32 platform_poll_menu_command(PlatformWindow *window) {
    S32 cmd = window->pending_menu_cmd;
    window->pending_menu_cmd = 0;
    return cmd;
}

PlatformInput platform_get_input(PlatformWindow *window) {
    PlatformInput result = window->input;

    // Poll mouse position
    POINT cursor;
    GetCursorPos(&cursor);
    ScreenToClient(window->hwnd, &cursor);
    result.mouse_pos = v2f32((F32)cursor.x, (F32)cursor.y);

    // Poll mouse button
    result.was_mouse_down = window->prev_mouse_down;
    result.mouse_down = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
    window->prev_mouse_down = result.mouse_down;

    // Clear accumulated events for next frame
    memset(&window->input, 0, sizeof(window->input));
    return result;
}

B32 platform_window_should_close(PlatformWindow *window) {
    return window ? window->should_close : 0;
}

void platform_focus_window(PlatformWindow *window) {
    if (!window || !window->hwnd) return;
    SetForegroundWindow(window->hwnd);
    SetFocus(window->hwnd);
}

F32 platform_get_dpi_scale(PlatformWindow *window) {
    if (!window || !window->hwnd) return 1.0f;
    return (F32)GetDpiForWindow(window->hwnd) / 96.0f;
}

void platform_set_cursor(PlatformCursor cursor) {
    switch (cursor) {
    case PLATFORM_CURSOR_SIZE_WE: g_current_cursor = LoadCursor(NULL, IDC_SIZEWE); break;
    case PLATFORM_CURSOR_SIZE_NS: g_current_cursor = LoadCursor(NULL, IDC_SIZENS); break;
    default:                      g_current_cursor = LoadCursor(NULL, IDC_ARROW);   break;
    }
}

void platform_clipboard_set(const char *text) {
    if (!text) return;
    int len = (S32)strlen(text);
    if (len == 0) return;

    // Convert UTF-8 to wide string for Windows clipboard
    int wlen = MultiByteToWideChar(CP_UTF8, 0, text, len, NULL, 0);
    if (wlen == 0) return;

    HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE, (wlen + 1) * sizeof(wchar_t));
    if (!hmem) return;

    wchar_t *wbuf = (wchar_t *)GlobalLock(hmem);
    MultiByteToWideChar(CP_UTF8, 0, text, len, wbuf, wlen);
    wbuf[wlen] = L'\0';
    GlobalUnlock(hmem);

    HWND hwnd = g_main_window ? g_main_window->hwnd : NULL;
    if (OpenClipboard(hwnd)) {
        EmptyClipboard();
        SetClipboardData(CF_UNICODETEXT, hmem);
        CloseClipboard();
    } else {
        GlobalFree(hmem);
    }
}

const char *platform_clipboard_get() {
    static char buf[4096];
    buf[0] = '\0';

    HWND hwnd = g_main_window ? g_main_window->hwnd : NULL;
    if (!OpenClipboard(hwnd)) return NULL;

    HGLOBAL hmem = GetClipboardData(CF_UNICODETEXT);
    if (hmem) {
        wchar_t *wbuf = (wchar_t *)GlobalLock(hmem);
        if (wbuf) {
            int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf) - 1, NULL, NULL);
            buf[len > 0 ? len - 1 : 0] = '\0'; // WideCharToMultiByte includes null in count
            GlobalUnlock(hmem);
        }
    }

    CloseClipboard();
    return buf[0] ? buf : NULL;
}

S32 platform_message_box(PlatformWindow *parent, const char *title,
                         const char *message, PlatformMsgBoxType type) {
    UINT mb_type;
    switch (type) {
    case PLATFORM_MSGBOX_OK:        mb_type = MB_OK; break;
    case PLATFORM_MSGBOX_OK_CANCEL: mb_type = MB_OKCANCEL; break;
    case PLATFORM_MSGBOX_YES_NO:    mb_type = MB_YESNO; break;
    default:                        mb_type = MB_OK; break;
    }

    // Convert UTF-8 to wide strings
    int title_wlen = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0);
    wchar_t *wtitle = (wchar_t *)_malloca(title_wlen * sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, title, -1, wtitle, title_wlen);

    int msg_wlen = MultiByteToWideChar(CP_UTF8, 0, message, -1, NULL, 0);
    wchar_t *wmsg = (wchar_t *)_malloca(msg_wlen * sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, message, -1, wmsg, msg_wlen);

    HWND hwnd = parent ? parent->hwnd : NULL;
    int result = MessageBoxW(hwnd, wmsg, wtitle, mb_type);

    _freea(wmsg);
    _freea(wtitle);

    switch (result) {
    case IDOK:     return 0;
    case IDYES:    return 0;
    case IDCANCEL: return 1;
    case IDNO:     return 1;
    default:       return -1;
    }
}
