#include "platform/platform.h"

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

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

static PlatformWindow *g_current_window = nullptr;
static HCURSOR g_current_cursor = nullptr;

static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    switch (msg) {
    case WM_SIZE:
        if (g_current_window && wparam != SIZE_MINIMIZED) {
            g_current_window->width  = (S32)LOWORD(lparam);
            g_current_window->height = (S32)HIWORD(lparam);
            // Render a frame during the modal resize loop so the UI
            // stays responsive instead of showing a stretched image.
            if (g_current_window->frame_callback) {
                g_current_window->frame_callback(g_current_window->frame_callback_user_data);
            }
        }
        return 0;
    case WM_CHAR:
        if (g_current_window && wparam >= 32 && wparam < 0xFFFF) {
            PlatformInput *ev = &g_current_window->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 (g_current_window) {
            PlatformInput *ev = &g_current_window->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 (g_current_window) {
            S16 wheel_delta = (S16)HIWORD(wparam);
            g_current_window->input.scroll_delta.y += (F32)wheel_delta / (F32)WHEEL_DELTA * 6.0f;
        }
        return 0;
    case WM_COMMAND:
        if (g_current_window && HIWORD(wparam) == 0)
            g_current_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(nullptr, IDC_ARROW));
            return TRUE;
        }
        break;
    case WM_DPICHANGED:
        if (g_current_window) {
            RECT *suggested = (RECT *)lparam;
            SetWindowPos(hwnd, nullptr, suggested->left, suggested->top,
                suggested->right - suggested->left, suggested->bottom - suggested->top,
                SWP_NOZORDER | SWP_NOACTIVATE);
        }
        return 0;
    case WM_CLOSE:
        if (g_current_window)
            g_current_window->should_close = true;
        return 0;
    case WM_DESTROY:
        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);

    WNDCLASSEXW wc = {};
    wc.cbSize        = sizeof(wc);
    wc.style         = CS_CLASSDC;
    wc.lpfnWndProc   = win32_wndproc;
    wc.hInstance     = GetModuleHandleW(nullptr);
    wc.hCursor       = LoadCursor(nullptr, IDC_ARROW);
    wc.lpszClassName = L"autosample_wc";
    RegisterClassExW(&wc);

    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;

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

    int wchar_count = MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, nullptr, 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, wc.lpszClassName, wtitle,
        WS_OVERLAPPEDWINDOW,
        x, y,
        rect.right - rect.left,
        rect.bottom - rect.top,
        nullptr, nullptr, wc.hInstance, nullptr
    );

    _freea(wtitle);

    if (!hwnd)
        return nullptr;

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

    PlatformWindow *window = new PlatformWindow();
    window->hwnd         = hwnd;
    window->should_close = false;
    window->width        = desc->width;
    window->height       = desc->height;

    g_current_window = window;
    return window;
}

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

    if (window->hwnd) {
        DestroyWindow(window->hwnd);
        UnregisterClassW(L"autosample_wc", GetModuleHandleW(nullptr));
    }

    if (g_current_window == window)
        g_current_window = nullptr;

    delete window;
}

B32 platform_poll_events(PlatformWindow *window) {
    MSG msg;
    while (PeekMessageW(&msg, nullptr, 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, nullptr);
            } else {
                int wchar_count = MultiByteToWideChar(CP_UTF8, 0, item->label, -1, nullptr, 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, nullptr, 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
    window->input = {};
    return result;
}

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(nullptr, IDC_SIZEWE); break;
    case PLATFORM_CURSOR_SIZE_NS: g_current_cursor = LoadCursor(nullptr, IDC_SIZENS); break;
    default:                      g_current_cursor = LoadCursor(nullptr, 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, nullptr, 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_current_window ? g_current_window->hwnd : nullptr;
    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_current_window ? g_current_window->hwnd : nullptr;
    if (!OpenClipboard(hwnd)) return nullptr;

    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, nullptr, nullptr);
            buf[len > 0 ? len - 1 : 0] = '\0'; // WideCharToMultiByte includes null in count
            GlobalUnlock(hmem);
        }
    }

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