/*
HelloDom — exercises every public surface of the DOM partition that
absorbed Crafter.CppDOM:

  * `Window` as a page-level event hub (no rendering — that's V2 / WebGPU).
  * `HtmlElement::CreateInBody` for element creation (new in this lib;
    CppDOM was query-only).
  * `HtmlElementPtr::Add*Listener` with auto-cleanup on destruction.
  * `Router::PushState` / `AddPopStateListener` / `GetPath` driving a
    real SPA — each route swaps the main content area without a server
    round-trip.

Serve the bin dir over HTTP, open index.html, navigate between Home /
About via the in-page links, hit back/forward, watch mouse/keyboard
events log into the box at the bottom.
*/

import Crafter.Graphics;
import Crafter.Event;
import std;
using namespace Crafter;

// ─── SPA scaffolding ──────────────────────────────────────────────────
// The full app shape:
//
//    [ nav: Home | About ]
//    ────────────────────
//    [ main — replaced per route ]
//    ────────────────────
//    [ log — append-only event trace ]
//
// `static` storage on the C++ side keeps the HtmlElements alive for the
// page's whole lifetime (their destructors would otherwise yank the
// nodes out of the DOM and unregister listeners when main() returns).
// Window itself is also `static` because the JS bridge stashes a raw
// pointer to it on construction; stack-local would leave that pointer
// dangling as soon as main returned.

static std::string log;
static void Append(std::string_view line) {
    log += std::string(line) + "\n";
    Dom::HtmlElementPtr pre("hello-log");
    pre.SetInnerHTML(log);
}

// Elements scoped to the currently-rendered route. `HtmlElementPtr`
// auto-removes its registered listeners when destroyed (the V1 fix
// for CppDOM's silent-leak class of bug), so any element we want a
// click handler on must outlive `Render()` — otherwise the destructor
// kicks in immediately and the listener dies before the user can click.
// Clearing this list at the top of every Render() detaches the previous
// route's bindings; pushing into it within a branch keeps the new
// route's bindings alive until the next navigation.
static std::vector<Dom::HtmlElementPtr> routeBindings;

static void Render(std::string_view path) {
    routeBindings.clear();
    Dom::HtmlElementPtr main("hello-main");
    if (path == "/" || path.empty()) {
        main.SetInnerHTML(R"(
            <h2>Home</h2>
            <p>This is the home route. Click <b>About</b> above to
               navigate without reloading.</p>
            <button id="hello-btn">Click me</button>
        )");
        Dom::HtmlElementPtr& btn = routeBindings.emplace_back("hello-btn");
        btn.AddClickListener([](Dom::MouseEvent e) {
            Append(std::format("button click @ ({:.0f},{:.0f})", e.clientX, e.clientY));
        });
    } else if (path == "/about") {
        main.SetInnerHTML(R"(
            <h2>About</h2>
            <p>HelloDom is the demo for Crafter.Graphics's
               <code>CRAFTER_GRAPHICS_WINDOW_DOM</code> mode. The whole
               page — content, routing, event handling — is compiled
               C++ running in WebAssembly.</p>
        )");
    } else {
        main.SetInnerHTML(std::format(R"(
            <h2>Not found</h2>
            <p>No route registered for <code>{}</code>.</p>
        )", path));
    }
    Append(std::format("rendered route: {}", path));
}

// Used by both the nav click handlers and (indirectly) the popstate
// listener. Mutates browser history, then re-renders.
static void Navigate(std::string_view url) {
    Router::PushState("{}", "", url);
    Render(url);
}

int main() {
    Device::Initialize();
    static Window window(0, 0, "HelloDom");

    // Build the persistent page chrome. The route links use plain
    // <span class="route-link"> (no <a href>) so the browser does NOT
    // navigate when they're clicked — only our handler runs. This is
    // the canonical SPA pattern; if you want right-click-open-in-new-tab
    // you'd switch to <a href> and call preventDefault in a custom JS
    // intercept (not exposed in V1).
    static Dom::HtmlElement root = Dom::HtmlElement::CreateInBody("div", "hello-root");
    root.SetStyle("font-family:system-ui,sans-serif;padding:24px;max-width:640px;");
    root.SetInnerHTML(R"(
        <nav style="margin-bottom:16px">
            <span id="link-home"  class="route-link" style="cursor:pointer;text-decoration:underline;margin-right:12px">Home</span>
            <span id="link-about" class="route-link" style="cursor:pointer;text-decoration:underline">About</span>
        </nav>
        <div id="hello-main" style="min-height:120px;padding:12px;border:1px solid #ddd;border-radius:6px"></div>
        <h3 style="margin-top:24px;font-size:14px;text-transform:uppercase;color:#666">Event log</h3>
        <pre id="hello-log" style="background:#eee;padding:8px;height:160px;overflow:auto;font-size:12px"></pre>
    )");

    // Nav link click handlers. `static` so they outlive main(); the
    // <span>s themselves are persistent (we never SetInnerHTML on
    // <nav>), so the same listener fires for every nav click.
    static Dom::HtmlElementPtr linkHome("link-home");
    static Dom::HtmlElementPtr linkAbout("link-about");
    linkHome .AddClickListener([](Dom::MouseEvent) { Navigate("/"); });
    linkAbout.AddClickListener([](Dom::MouseEvent) { Navigate("/about"); });

    // Browser back / forward: re-render whatever the URL now points to.
    Router::AddPopStateListener([] {
        std::string path = Router::GetPath();
        Append(std::format("popstate → {}", path));
        Render(path);
    });

    // Initial render — boot into whatever path the page loaded with.
    Append("page initialised");
    Render(Router::GetPath());

    // Page-level events come through Window. KeyDown fires for every
    // key press regardless of focus; matches Wayland / Win32 semantics.
    static EventListener<KeyCode> keyDownSub(&window.onRawKeyDown,
        [](KeyCode code) {
            Append(std::format("key down: 0x{:x}", code));
        });
    static EventListener<std::uint32_t> scrollSub(&window.onMouseScroll,
        [](std::uint32_t delta) {
            Append(std::format("scroll: {}", static_cast<std::int32_t>(delta)));
        });
    static EventListener<void> resizeSub(&window.onResize,
        []{ Append("resize"); });

    // Kick off the rAF loop. Returns immediately on DOM; the wasm
    // module stays alive while the JS bridge ticks frames.
    window.StartUpdate();
    window.StartSync();
    return 0;
}
