/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*/

// Browser DOM bindings — absorbed from Crafter.CppDOM. Only meaningful
// when the build defined CRAFTER_GRAPHICS_WINDOW_DOM (i.e. a wasm32-*
// target paired with additional/dom-env.js); on native platforms the
// partition still parses but every method links to nothing because the
// build excludes the impl. project.cpp's per-target module set is what
// makes the partition disappear from the native libs.
//
// Two-class hierarchy (simplified from CppDOM's three-class design):
//   HtmlElementPtr — non-owning element reference. Tracks every handler
//                    it registered and clears them on destruction, so
//                    the silent-leak bug from CppDOM's bare-pointer
//                    flavour can't happen. Releases the JS handle on
//                    destruction.
//   HtmlElement    — `HtmlElementPtr` plus element ownership: removes
//                    the element from the DOM on destruction. The new
//                    `Create` factory builds a fresh element under a
//                    parent and returns one of these.
//
// Move-only throughout. The JS handle (`ptr`) of a moved-from instance
// is zeroed so the destructor of the moved-from carcass is a no-op.

export module Crafter.Graphics:Dom;
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
import std;
import :DomEvents;

export namespace Crafter::Dom {

    class HtmlElementPtr {
    public:
        // Opaque JS-side element cookie. 0 = "no element"; the destructor
        // treats 0 as "moved-from, nothing to release".
        std::int32_t ptr;

        // Look up an existing element by its DOM id.
        HtmlElementPtr(const std::string_view id);
        // Look up + replace innerHTML in one step (matches CppDOM).
        HtmlElementPtr(const std::string_view id, const std::string_view html);

    protected:
        // Adopt a JS handle directly (used by HtmlElement::Create — the
        // JS side has already minted the handle, no second lookup needed).
        // Tagged-type ctor so the public id-based overloads stay
        // unambiguous from the call site.
        struct FromHandle { std::int32_t handle; };
        explicit HtmlElementPtr(FromHandle h) noexcept : ptr(h.handle) {}

    public:

        // Move-only — copying would silently double-free the JS handle.
        HtmlElementPtr(HtmlElementPtr&&) noexcept;
        HtmlElementPtr& operator=(HtmlElementPtr&&) noexcept;
        HtmlElementPtr(const HtmlElementPtr&) = delete;
        HtmlElementPtr& operator=(const HtmlElementPtr&) = delete;

        ~HtmlElementPtr();

        // DOM ops ─────────────────────────────────────────────────────
        void SetInnerHTML(const std::string_view html);
        void SetStyle(const std::string_view style);
        void SetProperty(const std::string_view property, const std::string_view value);
        void AddClass(const std::string_view className);
        void RemoveClass(const std::string_view className);
        void ToggleClass(const std::string_view className);
        bool HasClass(const std::string_view className);
        std::string GetValue();
        void SetValue(const std::string_view value);

        // Listener API — each Add* returns an opaque id that can be
        // passed to the matching Remove*. The destructor automatically
        // removes every handler still registered, so manual removal is
        // optional. Returns 0 only if registration failed at the JS
        // boundary (the element was already collected). The 23 event
        // types are 1:1 with CppDOM's surface.
        std::int32_t AddClickListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveClickListener(std::int32_t id);

        std::int32_t AddMouseOverListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveMouseOverListener(std::int32_t id);

        std::int32_t AddMouseOutListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveMouseOutListener(std::int32_t id);

        std::int32_t AddMouseMoveListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveMouseMoveListener(std::int32_t id);

        std::int32_t AddMouseDownListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveMouseDownListener(std::int32_t id);

        std::int32_t AddMouseUpListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveMouseUpListener(std::int32_t id);

        std::int32_t AddFocusListener(std::function<void(Crafter::Dom::FocusEvent)> callback);
        void         RemoveFocusListener(std::int32_t id);

        std::int32_t AddBlurListener(std::function<void(Crafter::Dom::FocusEvent)> callback);
        void         RemoveBlurListener(std::int32_t id);

        std::int32_t AddKeyDownListener(std::function<void(Crafter::Dom::KeyboardEvent)> callback);
        void         RemoveKeyDownListener(std::int32_t id);

        std::int32_t AddKeyUpListener(std::function<void(Crafter::Dom::KeyboardEvent)> callback);
        void         RemoveKeyUpListener(std::int32_t id);

        std::int32_t AddKeyPressListener(std::function<void(Crafter::Dom::KeyboardEvent)> callback);
        void         RemoveKeyPressListener(std::int32_t id);

        std::int32_t AddChangeListener(std::function<void(Crafter::Dom::ChangeEvent)> callback);
        void         RemoveChangeListener(std::int32_t id);

        std::int32_t AddSubmitListener(std::function<void()> callback);
        void         RemoveSubmitListener(std::int32_t id);

        std::int32_t AddInputListener(std::function<void(Crafter::Dom::InputEvent)> callback);
        void         RemoveInputListener(std::int32_t id);

        std::int32_t AddResizeListener(std::function<void(Crafter::Dom::ResizeEvent)> callback);
        void         RemoveResizeListener(std::int32_t id);

        std::int32_t AddScrollListener(std::function<void(Crafter::Dom::ScrollEvent)> callback);
        void         RemoveScrollListener(std::int32_t id);

        std::int32_t AddContextMenuListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveContextMenuListener(std::int32_t id);

        std::int32_t AddDragStartListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveDragStartListener(std::int32_t id);

        std::int32_t AddDragEndListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveDragEndListener(std::int32_t id);

        std::int32_t AddDropListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveDropListener(std::int32_t id);

        std::int32_t AddDragOverListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveDragOverListener(std::int32_t id);

        std::int32_t AddDragEnterListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveDragEnterListener(std::int32_t id);

        std::int32_t AddDragLeaveListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
        void         RemoveDragLeaveListener(std::int32_t id);

        std::int32_t AddWheelListener(std::function<void(Crafter::Dom::WheelEvent)> callback);
        void         RemoveWheelListener(std::int32_t id);

    protected:
        // Per-event-kind handler-id lists. Each `Add*Listener` push_backs
        // the id it returns; `RemoveAllHandlers` walks every list and
        // calls the matching JS Remove on each id. The lists are indexed
        // by event kind so a single handler-id collision across kinds
        // (allowed by the JS bridge — counters are per-event-kind)
        // doesn't cause a wrong-kind remove.
        std::vector<std::int32_t> handlerIds_[24];

        // Shared cleanup used by the destructor AND by move-assignment.
        // Removes every registered handler from both the C++ map and
        // the JS side, then frees the JS handle. Leaves `ptr == 0` on
        // return so a second call is a no-op.
        void RemoveAllHandlersAndFree();
    };

    class HtmlElement : public HtmlElementPtr {
    public:
        // Adopt an existing element by id; the dtor will remove it from
        // the DOM. Useful when the caller created the element in markup
        // and wants C++ to own its lifetime from now on.
        HtmlElement(const std::string_view id);
        HtmlElement(const std::string_view id, const std::string_view html);

        HtmlElement(HtmlElement&&) noexcept;
        HtmlElement& operator=(HtmlElement&&) noexcept;

        ~HtmlElement();

        // Create a new element and append it to `parent`. Optional `id`
        // sets the new element's DOM id (use it when you want to look the
        // element up from non-C++ code later); empty leaves the element
        // unnamed. Returns an owning handle — the element is removed from
        // the DOM when this `HtmlElement` is destroyed.
        static HtmlElement Create(const HtmlElementPtr& parent,
                                  std::string_view tagName,
                                  std::string_view id = {});

        // Convenience: create a top-level element under document.body.
        static HtmlElement CreateInBody(std::string_view tagName,
                                        std::string_view id = {});

    private:
        // Hidden ctor used by Create/CreateInBody — takes an already
        // allocated JS handle via the base class FromHandle tag. Kept
        // private so user code doesn't construct an HtmlElement from a
        // raw handle by accident.
        explicit HtmlElement(HtmlElementPtr::FromHandle h) noexcept
            : HtmlElementPtr(h) {}
    };
}
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
