// Smoke test for the Tier 1+2+3 UI architecture. Opens a window, draws a
// background, a button (Tier 3 component), a slider (Tier 3 component), a
// progress bar (Tier 3 component), and a circle that follows the mouse
// (Tier 2 standard shader, dispatched directly). Hit-testing for the button
// and slider is the user's responsibility — see the onMouseMove listener.

#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
#include "vulkan/vulkan.h"
#endif

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

int main() {
    Device::Initialize();
    #ifdef CRAFTER_GRAPHICS_WINDOW_DOM
    static Window window(1280, 720, "Hello UI");
    #else
    Window window(1280, 720, "Hello UI");
    #endif

    auto init = window.StartInit();

    GraphicsDescriptorHeap heap;
    heap.Initialize(/*images*/ 16, /*buffers*/ 16, /*samplers*/ 4);
    window.descriptorHeap = &heap;

    Font font("font.ttf");

    FontAtlas atlas;
    atlas.Initialize(init);

    UIRenderer ui;
    ui.fontAtlas = &atlas;
    ui.Initialize(window, heap, init);
    window.passes.push_back(&ui);

    // User-owned per-shader buffers. Mapped, written each frame, dispatched
    // by the user. Capacity is up to the user; resize means re-Register.
    GraphicsBuffer<QuadItem,   true> quadsBuf;
    GraphicsBuffer<CircleItem, true> circlesBuf;
    GraphicsBuffer<GlyphItem,  true> glyphsBuf;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
    quadsBuf.Create(
        VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 256);
    circlesBuf.Create(
        VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 64);
    glyphsBuf.Create(
        VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 4096);
#else
    quadsBuf.Create(256);
    circlesBuf.Create(64);
    glyphsBuf.Create(4096);
#endif

    auto quadsSlot   = ui.RegisterBuffer(quadsBuf);
    auto circlesSlot = ui.RegisterBuffer(circlesBuf);
    auto glyphsSlot  = ui.RegisterBuffer(glyphsBuf);

    // Application-side state. Library doesn't track this; the user owns it.
    Rect btnRect{};
    Rect sliderRect{};
    bool hovered      = false;
    bool sliderHovered = false;
    bool dragging     = false;
    float sliderT     = 0.42f;
    float progress    = 0.0f;

    // Hit-testing — purely user code. EventListener objects hold the
    // subscription; their destructors unregister at scope exit.
    EventListener<void> moveSub(&window.onMouseMove, [&]() {
        float mx = window.currentMousePos.x;
        float my = window.currentMousePos.y;
        hovered       = btnRect.Contains(mx, my);
        sliderHovered = sliderRect.Contains(mx, my);
        if (dragging && sliderRect.w > 0) {
            sliderT = std::clamp((mx - sliderRect.x) / sliderRect.w, 0.0f, 1.0f);
        }
    });
    EventListener<void> clickSub(&window.onMouseLeftClick, [&]() {
        if (sliderHovered) dragging = true;
    });
    EventListener<void> releaseSub(&window.onMouseLeftRelease, [&]() {
        dragging = false;
    });

    // Application color palette. No library Theme — just a struct.
    ButtonColors btnPalette{
        .bg              = {0.20f, 0.22f, 0.28f, 1.0f},
        .bgHover         = {0.30f, 0.55f, 0.95f, 1.0f},
        .bgPressed       = {0.18f, 0.40f, 0.78f, 1.0f},
        .text            = {1.0f,  1.0f,  1.0f,  1.0f},
        .border          = {0.0f,  0.0f,  0.0f,  0.0f},
        .cornerRadius    = 8.0f,
        .borderThickness = 0.0f,
    };
    SliderColors sliderPalette{
        .track       = {0.20f, 0.20f, 0.25f, 1.0f},
        .trackFilled = {0.30f, 0.55f, 0.95f, 1.0f},
        .thumb       = {0.85f, 0.85f, 0.90f, 1.0f},
        .thumbHover  = {1.00f, 1.00f, 1.00f, 1.0f},
        .trackHeight = 6.0f,
        .thumbRadius = 10.0f,
    };
    ProgressColors progressPalette{
        .bg           = {0.15f, 0.15f, 0.18f, 1.0f},
        .fill         = {0.40f, 0.85f, 0.50f, 1.0f},
        .cornerRadius = 4.0f,
    };

    EventListener<UIBuildArgs> buildSub(&ui.onBuild, [&](UIBuildArgs a) {
        auto cmd = a.cmd;

        // Update demo progress.
        progress = std::fmod(progress + 0.005f, 1.0f);

        // Layout via SubRect — resize-safe.
        Rect canvas = Rect::FromWindow(window);
        Rect topBar = canvas.SubRect(80, Rect::Anchor::Top).Inset(20, 20, 10, 20);
        btnRect    = topBar.SubRect(160, Rect::Anchor::Left);
        sliderRect = canvas.Inset(60).SubRect(20, Rect::Anchor::Top);
        sliderRect.y = canvas.h * 0.5f;
        sliderRect.x = 60.0f;
        sliderRect.w = canvas.w - 120.0f;
        Rect progressRect{ 60.0f, canvas.h * 0.5f + 60.0f, canvas.w - 120.0f, 16.0f };

        // Reset per-frame counters.
        std::uint32_t qc = 0, cc = 0, gc = 0;

        UIBuffer buf{
            .quads      = quadsBuf.value,
            .quadCount  = &qc,
            .quadCap    = 256,
            .glyphs     = glyphsBuf.value,
            .glyphCount = &gc,
            .glyphCap   = 4096,
            .atlas      = &atlas,
            .renderer   = &ui,
        };

        // Background quad — required because the swapchain is not cleared
        // (no TRANSFER_DST_BIT on the swapchain image).
        QuadItem bg{
            canvas.x, canvas.y, canvas.w, canvas.h,
            0.f, 0, 0, 1.f,
            0, 0, 0, 0,
            0, 0, 0, 0,
        };
        if (qc < buf.quadCap) buf.quads[qc++] = bg;

        // Tier 3 components.
        DrawButton(buf, btnRect,
                   hovered ? "Hovered!" : "Hover me",
                   hovered, /*pressed*/ false,
                   font, 18.0f, btnPalette);

        DrawSlider(buf, sliderRect, sliderT, dragging, sliderPalette);

        DrawProgressBar(buf, progressRect, progress, progressPalette);

        // Tier 2 standard shader, used directly: a circle following the mouse.
        circlesBuf.value[cc++] = CircleItem{
            window.currentMousePos.x, window.currentMousePos.y, 6.0f, 0.0f,
            1.0f, 1.0f, 1.0f, 0.85f,
            0, 0, 0, 0,
        };

        // Flush the buffers the GPU is about to read, then composite all three
        // categories — background quads, the mouse circle, and the button/label
        // text — in ONE fused dispatch (issue #47). The frame runs these passes
        // back-to-back with no custom shader interleaved, so DispatchFused loads
        // and stores the swapchain image once instead of three times and skips
        // the two inter-pass barriers DispatchQuads→Circles→Text would insert.
        // Canonical order is quads → circles → images → text; here there are no
        // images, so that category is a free no-op.
        if (qc > 0) {
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
            quadsBuf.FlushDevice(cmd, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
#else
            quadsBuf.FlushDevice();
#endif
        }
        if (cc > 0) {
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
            circlesBuf.FlushDevice(cmd, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
#else
            circlesBuf.FlushDevice();
#endif
        }
        if (gc > 0) {
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
            glyphsBuf.FlushDevice(cmd, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
#else
            glyphsBuf.FlushDevice();
#endif
        }
        ui.DispatchFused(cmd,
                         {quadsSlot,   qc},
                         {circlesSlot, cc},
                         {},                  // no images this frame
                         {glyphsSlot,  gc});
    });

    window.FinishInit();
    window.Render();
    window.StartUpdate();
    window.StartSync();
}