/*
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
*/
module;

#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/input-event-codes.h>
#include <xkbcommon/xkbcommon.h>
#include "../lib/xdg-shell-client-protocol.h"
#include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "../lib/fractional-scale-v1.h"
#include "../lib/viewporter.h"
#include <string.h>
#include <linux/input.h>
#include <sys/mman.h>
#include <wayland-cursor.h>
#include <errno.h>
#include <fcntl.h>
#include <print>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#endif
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
#include "vulkan/vulkan.h"
#endif
#ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
#include <windows.h>
#endif

export module Crafter.Graphics:Window;
import std;
import :Types;
import :Keys;
import Crafter.Event;
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
import :WebGPU;
import :DescriptorHeapWebGPU;
#endif

export namespace Crafter {
    #ifndef CRAFTER_GRAPHICS_WINDOW_DOM
    struct Semaphores {
		// Swap chain image presentation
		VkSemaphore presentComplete;
		// Command buffer submission and execution
		VkSemaphore renderComplete;
	};
    struct RenderPass;
    struct DescriptorHeapVulkan;
    #else
    struct RenderPass;
    struct DescriptorHeapWebGPU;
    #endif

    struct Window {
        FrameTime currentFrameTime;
        std::uint32_t width; 
        std::uint32_t height;
        std::chrono::time_point<std::chrono::high_resolution_clock> lastFrameBegin;
        Event<void> onClose;
        Event<void> onBeforeUpdate;
        Event<FrameTime> onUpdate;
        // Fires when the swapchain has been recreated for a new size.
        // width/height already reflect the new size. Passes that hold
        // descriptors referring to window.imageViews[] must re-write them
        // here (the old VkImage handles have been destroyed).
        Event<void> onResize;
        bool open = true;
        bool updating = false;
        // Currently-pressed raw key codes. The runtime stores raw platform
        // codes only (Win32 PS/2 scancode + extended bit; Wayland kernel
        // keycode). Cross-platform default bindings use `Key(CrafterKeys::X)`
        // from :Keys to obtain the right code at compile time.
        std::unordered_set<KeyCode> heldKeys;
        Event<KeyCode> onRawKeyDown;
        Event<KeyCode> onRawKeyHold;
        Event<KeyCode> onRawKeyUp;
        Event<const std::string_view> onTextInput;
        Event<void> onMouseRightClick;
        Event<void> onMouseLeftClick;
        Event<void> onMouseRightHold;
        Event<void> onMouseLeftHold;
        Event<void> onMouseRightRelease;
        Event<void> onMouseLeftRelease;
        Event<void> onMouseMove;
        Event<void> onMouseEnter;
        Event<void> onMouseLeave;
        // Mouse wheel, in whole detents per event, sign packed into the
        // uint32 payload (reinterpret as int32 to read it). Positive =
        // wheel down / toward the user — the DOM deltaY sign. Every
        // backend normalizes to ±1 per notch: dom-env.js divides
        // WheelEvent deltas by the per-deltaMode detent size, Wayland
        // divides the axis value by libinput's 15-units-per-detent,
        // Win32 divides by WHEEL_DELTA. Sub-detent motion (smooth-scroll
        // touchpads, free-spinning wheels) is accumulated backend-side
        // until a whole detent is reached.
        Event<std::uint32_t> onMouseScroll;
        Vector<float, 2> currentMousePos;
        Vector<float, 2> lastMousePos;
        Vector<float, 2> mouseDelta;
        bool mouseLeftHeld = false;
        bool mouseRightHeld = false;

        Window() = default;
        Window(std::uint32_t width, std::uint32_t height);
        Window(std::uint32_t width, std::uint32_t height, const std::string_view title);
        Window(Window&) = delete;
        Window(Window&&) = delete;
        Window& operator=(const Window&) = delete;
        #ifdef CRAFTER_GRAPHICS_WINDOW_DOM
        // DOM mode keeps a process-global pointer to the live Window so
        // the JS bridge can deliver document/window events back. If the
        // Window is destroyed (e.g. it was stack-allocated in main and
        // main returned), the global must be cleared so subsequent
        // browser callbacks become no-ops instead of dereferencing
        // freed memory. Native builds have nothing to clean up at the
        // Window level (Vulkan resources teardown is the user's job).
        ~Window();
        #endif

        void StartSync();
        void StartUpdate();
        void StopUpdate();
        void SetTitle(const std::string_view title);
        void Resize(std::uint32_t width, std::uint32_t height);
        void Render();
        void Update();
        // Replace the system cursor with a custom image. `pixels` is
        // `width*height*4` bytes in R8G8B8A8 memory order (matching
        // stb_image's STBI_rgb_alpha output) with straight (non-premultiplied)
        // alpha — the conversion to the compositor's expected format is
        // handled internally. The hotspot is in image-pixel coordinates.
        // Re-callable at any time.
        void SetCursorImage(std::uint16_t width, std::uint16_t height,
                            std::uint16_t hotspotX, std::uint16_t hotspotY,
                            const std::uint8_t* pixels);

        // Restore the default system cursor (releases any previously-uploaded
        // cursor pixel buffer).
        void SetDefaultCursor();

        #ifdef CRAFTER_TIMING
        std::chrono::nanoseconds totalUpdate;
        std::vector<std::pair<const EventListener<FrameTime>*, std::chrono::nanoseconds>> updateTimings;
        std::chrono::nanoseconds totalRender;
        std::chrono::nanoseconds vblank;
        std::chrono::nanoseconds totalFrame;
        std::chrono::time_point<std::chrono::high_resolution_clock> frameEnd;
        // Fixed-size ring buffer of the most recent frame times. LogTiming does
        // order-independent sum/avg/min/max, so head position is irrelevant to
        // the reported stats; this avoids the per-frame memmove a vector::erase
        // at the front would incur once full.
        static constexpr std::size_t frameTimeCapacity = 100;
        std::array<std::chrono::nanoseconds, frameTimeCapacity> frameTimes{};
        std::size_t frameTimesHead = 0;
        std::size_t frameTimesCount = 0;
        void LogTiming();
        #endif

        #ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
        HBITMAP cursorBitmap = nullptr;
        HCURSOR cursorHandle = nullptr;
        std::uint16_t cursorSizeX = 0;
        std::uint16_t cursorSizeY = 0;
        #endif

        #ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
        float scale = 1.0f;
        bool configured = false;
        // Pending size from the most recent xdg_toplevel.configure, in
        // surface-local (logical DP) units. Applied on xdg_surface.configure
        // via Resize(); 0 means "compositor has no preference, keep current".
        std::int32_t pendingLogicalWidth = 0;
        std::int32_t pendingLogicalHeight = 0;
        xdg_toplevel* xdgToplevel = nullptr;
        wp_viewport* wpViewport = nullptr;
        wl_surface* surface = nullptr;
        xdg_surface* xdgSurface = nullptr;
        wl_callback* cb = nullptr;
        wl_surface* cursorSurface = nullptr;
        wl_buffer* cursorWlBuffer = nullptr;
        std::uint32_t cursorBufferOldSize = 0;
        // mmap'd view of the SHM cursor buffer — the user-supplied pixels
        // are written here in BGRA8888 order. Lifetime matches cursorWlBuffer.
        std::uint8_t* cursorMmap_ = nullptr;
        std::uint16_t cursorHotspotX_ = 0;
        std::uint16_t cursorHotspotY_ = 0;
        // Most recent serial from a wl_pointer.enter on this window's surface.
        // Needed so `SetCursorImage` can re-issue `wl_pointer_set_cursor`
        // mid-session (the hotspot only updates when set_cursor is recalled).
        std::uint32_t lastPointerSerial_ = 0;

        static void xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_v1*, std::uint32_t scale);
        static void wl_surface_frame_done(void *data, wl_callback *cb, uint32_t time);
        static void xdg_toplevel_handle_close(void* data, xdg_toplevel*);
        static void xdg_surface_handle_configure(void* data, xdg_surface* xdg_surface, std::uint32_t serial);
        static void xdg_toplevel_configure(void*, xdg_toplevel*, std::int32_t, std::int32_t, wl_array*);

        constexpr static xdg_toplevel_listener xdg_toplevel_listener = {
            .configure = xdg_toplevel_configure,
            .close = xdg_toplevel_handle_close,
        };
        constexpr static wl_callback_listener wl_callback_listener = {
            .done = wl_surface_frame_done,
        };
        constexpr static xdg_surface_listener xdg_surface_listener = {
            .configure = xdg_surface_handle_configure,
        };
        constexpr static wp_fractional_scale_v1_listener wp_fractional_scale_v1_listener = {
            .preferred_scale = xdg_surface_handle_preferred_scale,
        };
        inline static wp_fractional_scale_v1* wp_scale = nullptr;
        #endif

        #ifndef CRAFTER_GRAPHICS_WINDOW_DOM
        VkCommandBuffer StartInit();
        void FinishInit();
        VkCommandBuffer GetCmd();
        void EndCmd(VkCommandBuffer cmd);
        void CreateSwapchain();
        // Tear-and-rebuild helper used by Resize() and the OUT_OF_DATE
        // recovery in Render(). Calls CreateSwapchain() and re-issues the
        // initial PRESENT_SRC_KHR layout transition for the new images so
        // Render()'s barriers (which assume oldLayout = PRESENT_SRC_KHR)
        // stay valid. Does NOT fire onResize — callers do that.
        void RecreateSwapchainAndImages();

        // Save the current swapchain image (state after Render() returns) to
        // a PNG file. Allocates a one-shot staging buffer + command buffer,
        // copies image-to-buffer, waits idle, then writes PNG via stb. Useful
        // for visual regression tests and screenshotting from headless code.
        void SaveFrame(const std::filesystem::path& path);
        static constexpr std::uint8_t numFrames = 3;
        VkSurfaceKHR vulkanSurface = VK_NULL_HANDLE;
        VkSwapchainKHR swapChain = VK_NULL_HANDLE;
        VkFormat colorFormat;
        VkColorSpaceKHR colorSpace;
        // Supported composite-alpha mode for vulkanSurface. A surface property
        // that does not change across swapchain recreation, so it is selected
        // once at construction rather than re-derived on every CreateSwapchain
        // (i.e. on every resize configure).
        VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
        VkImage images[numFrames];
        VkImageViewCreateInfo imageViews[numFrames];
        // Tracks whether each swapchain image has been rendered (and thus
        // left in PRESENT_SRC_KHR) at least once. Freshly created swapchain
        // images start in VK_IMAGE_LAYOUT_UNDEFINED, so the first per-frame
        // barrier must transition from UNDEFINED, not PRESENT_SRC_KHR.
        // Reset in CreateSwapchain(). A presentable image may only be touched
        // after it has been acquired, so this initial transition happens
        // lazily in Render() (post-acquire) rather than up front.
        std::array<bool, numFrames> imageInitialised{};
        std::thread thread;
        VkCommandBuffer drawCmdBuffers[numFrames];
        // Per-frame Vulkan info structs whose contents are almost entirely
        // invariant. Initialised once in the constructor; Render() patches only
        // the few fields that change each frame (the two barriers' image and
        // the acquire barrier's oldLayout, and — for the multi-frame-in-flight
        // sync below — submitInfo/presentInfo's wait/signal semaphores plus
        // submitInfo's command buffer, which follow currentBuffer/frameCounter).
        // presentInfo.pImageIndices and pSwapchains point at the
        // currentBuffer/swapChain members, so they track value changes
        // (including swapchain recreation) automatically.
        VkSubmitInfo submitInfo;
        VkCommandBufferBeginInfo cmdBufInfo;
        VkImageMemoryBarrier acquireBarrier;
        VkImageMemoryBarrier presentBarrier;
        VkPresentInfoKHR presentInfo;

        // ── Multi-frame-in-flight synchronisation (issue #40) ──────────────
        // The renderer keeps up to numFrames frames in flight, so a single
        // semaphore pair + a per-frame wait-idle is no longer enough. Note
        // that drawCmdBuffers, the per-frame descriptor heap slots, and the
        // swapchain images are ALL keyed by the acquired image index
        // (currentBuffer) — WriteSwapchainDescriptors bakes heap slot i to
        // write imageViews[i], so the index that selects a command buffer /
        // heap slot must equal the acquired image index. Everything below
        // follows from that.

        // Signaled by the queue submit, waited by vkQueuePresentKHR. Keyed by
        // the acquired IMAGE index: the presentation engine keeps this
        // semaphore in use until the image is re-acquired, so a per-image
        // semaphore is the only safe key. Keying it per-CPU-frame trips
        // VUID-vkQueueSubmit-pSignalSemaphores-00067.
        // https://docs.vulkan.org/guide/latest/swapchain_semaphore_reuse.html
        std::array<VkSemaphore, numFrames> renderFinishedSemaphores{};

        // Signaled by the queue submit, waited + reset before
        // drawCmdBuffers[image] and that image's descriptor-heap slot are
        // re-recorded. Keyed by image index. Created signaled so the first
        // wait on each image passes through instead of deadlocking.
        std::array<VkFence, numFrames> waitFences{};

        // Signaled by vkAcquireNextImageKHR, waited by the queue submit. Keyed
        // by a free-running CPU frame counter, because the image index isn't
        // known until acquire returns. Sized numFrames+1: at most numFrames
        // frames are ever in flight (each image's command buffer is gated by
        // its own fence, and there are numFrames images), so numFrames+1
        // distinct acquire semaphores guarantees the one being reused has no
        // pending operation — required by
        // VUID-vkAcquireNextImageKHR-semaphore-01779.
        static constexpr std::uint8_t numAcquireSemaphores = numFrames + 1;
        std::array<VkSemaphore, numAcquireSemaphores> imageAcquiredSemaphores{};

        // Free-running count of Render() calls; drives the acquire-semaphore
        // slot (frameCounter % numAcquireSemaphores).
        std::uint64_t frameCounter = 0;
        std::uint32_t currentBuffer = 0;
        VkPipelineStageFlags submitPipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
        std::vector<RenderPass*> passes;
        DescriptorHeapVulkan* descriptorHeap = nullptr;
        // Cached per-frame heap-bind structs. The heap address/size per slot
        // are stable once a heap is assigned; only currentBuffer varies frame
        // to frame, so the structs (and the reservedRangeOffset arithmetic)
        // are computed once per heap rather than every frame in Render().
        // Keyed on the heap pointer: whenever descriptorHeap differs from
        // cachedDescriptorHeap the cache is rebuilt. This invalidates on any
        // heap (re)assignment — heaps never resize today, so a stable pointer
        // means stable ranges. Not tied to onResize (the heap is independent
        // of swapchain size).
        VkBindHeapInfoEXT resourceHeapInfos[numFrames];
        VkBindHeapInfoEXT samplerHeapInfos[numFrames];
        DescriptorHeapVulkan* cachedDescriptorHeap = nullptr;
        std::optional<std::array<float, 4>> clearColor;
        #else
        // DOM mode: the page IS the window. WebGPU device and canvas are
        // owned JS-side (see additional/dom-webgpu.js); this struct just
        // holds the per-Window state Crafter::Window users expect:
        // a list of render passes and a pointer to the descriptor heap.
        static constexpr std::uint8_t numFrames = 1;
        std::uint32_t currentBuffer = 0;
        std::vector<RenderPass*> passes;
        DescriptorHeapWebGPU* descriptorHeap = nullptr;
        std::optional<std::array<float, 4>> clearColor;

        // DOM-mode StartInit/FinishInit are no-ops returning an opaque
        // command-buffer marker so cross-platform user code (HelloUI's
        // `auto init = window.StartInit();`) compiles unchanged.
        WebGPUCommandEncoderRef StartInit();
        void FinishInit();
        #endif
    };
}