/*
 * Windows WebView host.
 *
 * When BLUE_HAVE_WEBVIEW2 is defined (set by main.cpp when WEBVIEW2_SDK_PATH is
 * found), this file creates a real Win32 window hosting the WebView2 control and
 * registers the blue:// scheme for embedded-asset and bridge traffic — matching
 * the Linux GTK/WebKit2 implementation exactly.
 *
 * Without BLUE_HAVE_WEBVIEW2 the original MessageBox fallback is retained and a
 * stderr message tells the user how to enable the full WebView2 path.
 *
 * WEBVIEW2_SDK_PATH should point to the Microsoft.Web.WebView2 NuGet package root
 * (the directory that contains build/native/include/WebView2.h).
 */

#include "../jsc_webview.h"
#include "../jsc_embed.h"
#include "../jsc_runtime.h"

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string>
#include <cstring>
#include <cstdio>

/* WM_APP+1 is handled by our WndProc to run a function on the UI thread.
 * lParam points to an BlueWinDispatch struct defined in jsc_blue_native_win.cpp.
 * Defined here so the WndProc can handle it; the struct is defined in native_win. */
#define WM_BLUE_DISPATCH (WM_APP + 1)

struct BlueWinDispatch {
    void (*fn)(void*);
    void* arg;
};

/* Exposed to jsc_blue_native_win.cpp via extern. */
HWND  g_blue_host_hwnd    = NULL;
DWORD g_blue_ui_thread_id = 0;

static inline std::wstring utf8_to_wide_local(const char* s) {
    if (!s || !s[0]) return L"";
    int n = MultiByteToWideChar(CP_UTF8, 0, s, -1, nullptr, 0);
    std::wstring w(n, L'\0');
    MultiByteToWideChar(CP_UTF8, 0, s, -1, &w[0], n);
    if (!w.empty() && w.back() == L'\0') w.pop_back();
    return w;
}

/* ───────────────────────── WebView2 path ─────────────────────────────── */
#ifdef BLUE_HAVE_WEBVIEW2

#include <WebView2.h>
#include <wrl.h>
using namespace Microsoft::WRL;

static ICoreWebView2Environment* g_wv2_env    = nullptr;
static ICoreWebView2*            g_webview    = nullptr;
static ICoreWebView2Controller*  g_controller = nullptr;

static LRESULT CALLBACK BlueWndProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
    switch (msg) {
    case WM_BLUE_DISPATCH: {
        auto* d = reinterpret_cast<BlueWinDispatch*>(lp);
        if (d && d->fn) d->fn(d->arg);
        return 0;
    }
    case WM_SIZE:
        if (g_controller) {
            RECT r;
            GetClientRect(hw, &r);
            g_controller->put_Bounds(r);
        }
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProcW(hw, msg, wp, lp);
}

/* Converts a wchar_t URL path segment back to UTF-8 and handles the bridge
 * and embedded-asset cases, exactly as jsc_webview_linux.cpp does. */
static std::string wide_to_utf8(const wchar_t* w) {
    if (!w || !w[0]) return "";
    int n = WideCharToMultiByte(CP_UTF8, 0, w, -1, nullptr, 0, nullptr, nullptr);
    std::string s(n, '\0');
    WideCharToMultiByte(CP_UTF8, 0, w, -1, &s[0], n, nullptr, nullptr);
    if (!s.empty() && s.back() == '\0') s.pop_back();
    return s;
}

extern "C" void jsc_webview_run_modal(const char* title, const char* html,
                                       int width, int height) {
    g_blue_ui_thread_id = GetCurrentThreadId();

    /* Register window class */
    static bool registered = false;
    if (!registered) {
        WNDCLASSEXW wc = {};
        wc.cbSize        = sizeof(wc);
        wc.lpfnWndProc   = BlueWndProc;
        wc.hInstance     = GetModuleHandleW(nullptr);
        wc.hCursor       = LoadCursorW(nullptr, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszClassName = L"BlueWebViewHost";
        RegisterClassExW(&wc);
        registered = true;
    }

    std::wstring wtitle = utf8_to_wide_local(title && title[0] ? title : "blue app");

    HWND hw = CreateWindowExW(
        0, L"BlueWebViewHost", wtitle.c_str(),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, width > 0 ? width : 1024, height > 0 ? height : 768,
        nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
    if (!hw) return;

    g_blue_host_hwnd = hw;
    ShowWindow(hw, SW_SHOW);
    UpdateWindow(hw);

    /* Capture html into a std::string for use in the lambda. */
    std::string html_str = html ? html : "";

    bool wv_ready = false;
    HRESULT hr = CreateCoreWebView2EnvironmentWithOptions(
        nullptr, nullptr, nullptr,
        Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
            [hw, &html_str, &wv_ready](HRESULT, ICoreWebView2Environment* env) -> HRESULT {
                g_wv2_env = env;
                g_wv2_env->AddRef();
                env->CreateCoreWebView2Controller(hw,
                    Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
                        [hw, &html_str, &wv_ready](HRESULT, ICoreWebView2Controller* ctrl) -> HRESULT {
                            g_controller = ctrl;
                            ctrl->get_CoreWebView2(&g_webview);

                            RECT r;
                            GetClientRect(hw, &r);
                            ctrl->put_Bounds(r);

                            /* Register blue:// scheme filter for assets + bridge. */
                            g_webview->AddWebResourceRequestedFilter(
                                L"blue://*",
                                COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL);

                            EventRegistrationToken tok;
                            g_webview->add_WebResourceRequested(
                                Callback<ICoreWebView2WebResourceRequestedEventHandler>(
                                    [](ICoreWebView2* wv, ICoreWebView2WebResourceRequestedEventArgs* args) -> HRESULT {
                                        LPWSTR uriW = nullptr;
                                        args->get_Request()->get_Uri(&uriW);
                                        std::string uri = wide_to_utf8(uriW);
                                        CoTaskMemFree(uriW);

                                        /* Strip the scheme prefix: blue://app/ */
                                        static const std::string pfx = "blue://app/";
                                        std::string path = uri.size() > pfx.size() ? uri.substr(pfx.size()) : "";

                                        /* Bridge: __bridge__/fnName/encodedArg */
                                        static const std::string bridge_pfx = "__bridge__/";
                                        if (path.rfind(bridge_pfx, 0) == 0) {
                                            std::string rest = path.substr(bridge_pfx.size());
                                            auto slash = rest.find('/');
                                            std::string fn  = slash != std::string::npos ? rest.substr(0, slash) : rest;
                                            std::string arg = slash != std::string::npos ? rest.substr(slash + 1) : "";

                                            char* result = jsc_hybrid_call_aot_cstr(fn.c_str(), arg.c_str());
                                            std::string res_str = result ? result : "";
                                            free(result);

                                            /* Dispatch CustomEvent to frontend JS */
                                            std::string escaped;
                                            for (char c : res_str) {
                                                if (c == '\'') escaped += "\\'";
                                                else if (c == '\\') escaped += "\\\\";
                                                else if (c == '\n') escaped += "\\n";
                                                else escaped += c;
                                            }
                                            std::wstring js = utf8_to_wide_local(
                                                ("window.dispatchEvent(new CustomEvent('blue-backend',{detail:'" + escaped + "'}))").c_str());
                                            wv->ExecuteScript(js.c_str(), nullptr);

                                            /* Return empty 200 OK so the fetch() promise resolves. */
                                            if (g_wv2_env) {
                                                IStream* empty = SHCreateMemStream(nullptr, 0);
                                                if (empty) {
                                                    ComPtr<ICoreWebView2WebResourceResponse> resp;
                                                    g_wv2_env->CreateWebResourceResponse(
                                                        empty, 200, L"OK", L"Content-Type: text/plain", &resp);
                                                    args->put_Response(resp.Get());
                                                    empty->Release();
                                                }
                                            }
                                            return S_OK;
                                        }

                                        /* Embedded asset lookup */
                                        size_t data_len = 0;
                                        const char* mime = nullptr;
                                        const unsigned char* data = jsc_embed_lookup(path.c_str(), &data_len, &mime);
                                        if (!data) {
                                            args->put_Response(nullptr);
                                            return S_OK;
                                        }

                                        if (g_wv2_env) {
                                            IStream* body = SHCreateMemStream(data, (UINT)data_len);
                                            if (!body) { args->put_Response(nullptr); return S_OK; }
                                            std::wstring wmime = utf8_to_wide_local(mime ? mime : "application/octet-stream");
                                            ComPtr<ICoreWebView2WebResourceResponse> resp;
                                            g_wv2_env->CreateWebResourceResponse(
                                                body, 200, L"OK",
                                                (L"Content-Type: " + wmime).c_str(),
                                                &resp);
                                            args->put_Response(resp.Get());
                                            body->Release();
                                        }
                                        return S_OK;
                                    }).Get(), &tok);

                            /* Load the initial HTML */
                            std::wstring whtml = utf8_to_wide_local(html_str.c_str());
                            g_webview->NavigateToString(whtml.c_str());

                            wv_ready = true;
                            return S_OK;
                        }).Get());
                return S_OK;
            }).Get());

    if (FAILED(hr)) {
        fprintf(stderr, "blue: WebView2 environment creation failed (hr=0x%lx).\n"
                        "       Ensure the Edge WebView2 Runtime is installed.\n", (long)hr);
        g_blue_host_hwnd = NULL;
        DestroyWindow(hw);
        return;
    }

    MSG msg;
    while (GetMessageW(&msg, nullptr, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    g_blue_host_hwnd = NULL;
    if (g_webview)    { g_webview->Release();    g_webview    = nullptr; }
    if (g_controller) { g_controller->Release(); g_controller = nullptr; }
    if (g_wv2_env)    { g_wv2_env->Release();    g_wv2_env    = nullptr; }
}

/* ───────────────────────── Fallback (no WebView2 SDK) ───────────────── */
#else  /* BLUE_HAVE_WEBVIEW2 not defined */

static LRESULT CALLBACK BlueWndProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
    switch (msg) {
    case WM_BLUE_DISPATCH: {
        auto* d = reinterpret_cast<BlueWinDispatch*>(lp);
        if (d && d->fn) d->fn(d->arg);
        return 0;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProcW(hw, msg, wp, lp);
}

extern "C" void jsc_webview_run_modal(const char* title, const char* html,
                                       int width, int height) {
    (void)width; (void)height;
    (void)jsc_embed_lookup;
    fprintf(stderr,
        "blue: WebView2 SDK not found. Set WEBVIEW2_SDK_PATH to the\n"
        "       Microsoft.Web.WebView2 NuGet package root to enable the\n"
        "       full WebView2 window. Falling back to MessageBox preview.\n");

    /* Create a minimal invisible window so window/dialog/clipboard APIs work. */
    g_blue_ui_thread_id = GetCurrentThreadId();
    static bool registered = false;
    if (!registered) {
        WNDCLASSEXW wc = {};
        wc.cbSize        = sizeof(wc);
        wc.lpfnWndProc   = BlueWndProc;
        wc.hInstance     = GetModuleHandleW(nullptr);
        wc.lpszClassName = L"BlueWebViewHost";
        RegisterClassExW(&wc);
        registered = true;
    }
    HWND hw = CreateWindowExW(0, L"BlueWebViewHost",
        L"blue", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 1, 1,
        nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
    g_blue_host_hwnd = hw;

    std::wstring wtitle = utf8_to_wide_local(title && title[0] ? title : "blue app");
    std::wstring whtml  = utf8_to_wide_local(html ? html : "");
    MessageBoxW(hw, whtml.c_str(), wtitle.c_str(), MB_OK | MB_ICONINFORMATION);

    g_blue_host_hwnd = NULL;
    if (hw) DestroyWindow(hw);
}

#endif /* BLUE_HAVE_WEBVIEW2 */
#else  /* !_WIN32 */
extern "C" void jsc_webview_run_modal(const char*, const char*, int, int) {}
#endif /* _WIN32 */
