#include "renderer/renderer.h"
#include "ui/ui_core.h"
#include "ui/ui_icons.h"

#include <d3d12.h>
#include <dxgi1_5.h>
#include <d3dcompiler.h>
#include <math.h>

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>

// FreeType headers — temporarily undefine `internal` macro (base_core.h: #define internal static)
// because FreeType uses `internal` as a struct field name.
#undef internal
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#define internal static
#include "renderer/font_inter.gen.h"

#ifdef _DEBUG
#define DX12_ENABLE_DEBUG_LAYER
#endif

#ifdef DX12_ENABLE_DEBUG_LAYER
#include <dxgidebug.h>
#pragma comment(lib, "dxguid.lib")
#endif

#define NUM_BACK_BUFFERS  2
#define MAX_VERTICES      (64 * 1024)
#define MAX_INDICES       (MAX_VERTICES * 3)

// Font atlas
#define FONT_ATLAS_W      1024
#define FONT_ATLAS_H      1024
#define GLYPH_FIRST       32
#define GLYPH_LAST        126
#define GLYPH_COUNT       (GLYPH_LAST - GLYPH_FIRST + 1)

////////////////////////////////
// Vertex format for 2D UI rendering
// mode: 0 = SDF rounded rect, 1 = textured glyph

struct UIVertex {
    float pos[2];
    float uv[2];
    float col[4];
    float rect_min[2];
    float rect_max[2];
    float corner_radii[4]; // TL, TR, BR, BL
    float border_thickness;
    float softness;
    float mode; // 0 = rect SDF, 1 = textured
};

////////////////////////////////
// Glyph info

struct GlyphInfo {
    F32 u0, v0, u1, v1; // UV coords in atlas
    F32 w, h;            // pixel size
    F32 x_advance;       // how far to move cursor
};

////////////////////////////////
// Shaders (inline HLSL)

static const char *g_shader_hlsl = R"(
struct VSInput {
    float2 pos              : POSITION;
    float2 uv               : TEXCOORD0;
    float4 col              : COLOR0;
    float2 rect_min         : TEXCOORD1;
    float2 rect_max         : TEXCOORD2;
    float4 corner_radii     : TEXCOORD3;
    float  border_thickness : TEXCOORD4;
    float  softness         : TEXCOORD5;
    float  mode             : TEXCOORD6;
};

struct PSInput {
    float4 pos              : SV_POSITION;
    float2 uv               : TEXCOORD0;
    float4 col              : COLOR0;
    float2 rect_min         : TEXCOORD1;
    float2 rect_max         : TEXCOORD2;
    float4 corner_radii     : TEXCOORD3;
    float  border_thickness : TEXCOORD4;
    float  softness         : TEXCOORD5;
    float  mode             : TEXCOORD6;
};

cbuffer ConstantBuffer : register(b0) {
    float2 viewport_size;
    float2 _padding;
};

Texture2D    font_tex : register(t0);
SamplerState font_smp : register(s0);

PSInput VSMain(VSInput input) {
    PSInput output;
    float2 ndc;
    ndc.x = (input.pos.x / viewport_size.x) * 2.0 - 1.0;
    ndc.y = 1.0 - (input.pos.y / viewport_size.y) * 2.0;
    output.pos = float4(ndc, 0.0, 1.0);
    output.uv = input.uv;
    output.col = input.col;
    output.rect_min = input.rect_min;
    output.rect_max = input.rect_max;
    output.corner_radii = input.corner_radii;
    output.border_thickness = input.border_thickness;
    output.softness = input.softness;
    output.mode = input.mode;
    return output;
}

float rounded_rect_sdf(float2 sample_pos, float2 rect_center, float2 rect_half_size, float radius) {
    float2 d = abs(sample_pos - rect_center) - rect_half_size + float2(radius, radius);
    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;
}

float4 PSMain(PSInput input) : SV_TARGET {
    float4 col = input.col;

    if (input.mode > 1.5) {
        // RGBA textured mode: sample all channels, multiply by vertex color
        float4 tex = font_tex.Sample(font_smp, input.uv);
        col *= tex;
    } else if (input.mode > 0.5) {
        // Alpha-only textured mode: sample R channel as alpha (font atlas)
        float alpha = font_tex.Sample(font_smp, input.uv).r;
        col.a *= alpha;
    } else {
        // SDF rounded rect mode
        float2 pixel_pos = input.pos.xy;
        float2 rect_center = (input.rect_min + input.rect_max) * 0.5;
        float2 rect_half_size = (input.rect_max - input.rect_min) * 0.5;
        // corner_radii = (TL, TR, BR, BL) — select radius by quadrant
        float radius = (pixel_pos.x < rect_center.x)
            ? ((pixel_pos.y < rect_center.y) ? input.corner_radii.x : input.corner_radii.w)
            : ((pixel_pos.y < rect_center.y) ? input.corner_radii.y : input.corner_radii.z);
        float softness = max(input.softness, 0.5);
        float dist = rounded_rect_sdf(pixel_pos, rect_center, rect_half_size, radius);

        if (input.border_thickness > 0) {
            float inner_dist = dist + input.border_thickness;
            float outer_alpha = 1.0 - smoothstep(-softness, softness, dist);
            float inner_alpha = smoothstep(-softness, softness, inner_dist);
            col.a *= outer_alpha * inner_alpha;
        } else {
            col.a *= 1.0 - smoothstep(-softness, softness, dist);
        }
    }

    // Dither to reduce gradient banding (interleaved gradient noise)
    float dither = frac(52.9829189 * frac(dot(input.pos.xy, float2(0.06711056, 0.00583715)))) - 0.5;
    col.rgb += dither / 255.0;

    if (col.a < 0.002) discard;
    return col;
}
)";

////////////////////////////////
// Frame context

struct FrameContext {
    ID3D12CommandAllocator *command_allocator;
    UINT64                  fence_value;
};

////////////////////////////////
// Renderer struct

struct Renderer {
    HWND                         hwnd;
    S32                      width;
    S32                      height;
    S32                      frame_count;
    UINT                         frame_index;

    ID3D12Device                *device;
    ID3D12CommandQueue          *command_queue;
    IDXGISwapChain3             *swap_chain;
    HANDLE                       swap_chain_waitable;
    B32                         swap_chain_occluded;
    B32                         tearing_support;

    ID3D12DescriptorHeap        *rtv_heap;
    ID3D12DescriptorHeap        *srv_heap;

    FrameContext                 frames[NUM_BACK_BUFFERS];
    ID3D12Resource              *render_targets[NUM_BACK_BUFFERS];
    D3D12_CPU_DESCRIPTOR_HANDLE  rtv_descriptors[NUM_BACK_BUFFERS];

    ID3D12GraphicsCommandList   *command_list;
    ID3D12Fence                 *fence;
    HANDLE                       fence_event;
    UINT64                       fence_last_signaled;

    // UI rendering pipeline
    ID3D12RootSignature         *root_signature;
    ID3D12PipelineState         *pipeline_state;

    // Per-frame vertex/index buffers (F64-buffered)
    ID3D12Resource              *vertex_buffers[NUM_BACK_BUFFERS];
    ID3D12Resource              *index_buffers[NUM_BACK_BUFFERS];
    void                        *vb_mapped[NUM_BACK_BUFFERS];
    void                        *ib_mapped[NUM_BACK_BUFFERS];

    // Font atlas
    ID3D12Resource              *font_texture;
    GlyphInfo                    glyphs[GLYPH_COUNT];
    F32                          font_atlas_size; // font size the atlas was built at
    F32                          font_line_height;

    // Icon atlas
    ID3D12Resource              *icon_texture;
    ID3D12DescriptorHeap        *icon_srv_heap;

    // FreeType
    FT_Library                   ft_lib;
    FT_Face                      ft_face;

    // Clear color
    F32                        clear_r = 0.12f;
    F32                        clear_g = 0.12f;
    F32                        clear_b = 0.13f;
};

////////////////////////////////
// DX12 infrastructure

static B32 create_device(Renderer *r) {
#ifdef DX12_ENABLE_DEBUG_LAYER
    ID3D12Debug *debug = nullptr;
    if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug)))) {
        debug->EnableDebugLayer();
        debug->Release();
    }
#endif

    if (D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&r->device)) != S_OK)
        return false;

#ifdef DX12_ENABLE_DEBUG_LAYER
    {
        ID3D12InfoQueue *info_queue = nullptr;
        if (SUCCEEDED(r->device->QueryInterface(IID_PPV_ARGS(&info_queue)))) {
            info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true);
            info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true);
            info_queue->Release();
        }
    }
#endif

    return true;
}

static B32 create_command_queue(Renderer *r) {
    D3D12_COMMAND_QUEUE_DESC desc = {};
    desc.Type     = D3D12_COMMAND_LIST_TYPE_DIRECT;
    desc.Flags    = D3D12_COMMAND_QUEUE_FLAG_NONE;
    desc.NodeMask = 1;
    return r->device->CreateCommandQueue(&desc, IID_PPV_ARGS(&r->command_queue)) == S_OK;
}

static B32 create_descriptor_heaps(Renderer *r) {
    // RTV heap
    {
        D3D12_DESCRIPTOR_HEAP_DESC desc = {};
        desc.Type           = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
        desc.NumDescriptors = NUM_BACK_BUFFERS;
        desc.Flags          = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
        desc.NodeMask       = 1;
        if (r->device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&r->rtv_heap)) != S_OK)
            return false;

        SIZE_T rtv_size = r->device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
        D3D12_CPU_DESCRIPTOR_HANDLE handle = r->rtv_heap->GetCPUDescriptorHandleForHeapStart();
        for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
            r->rtv_descriptors[i] = handle;
            handle.ptr += rtv_size;
        }
    }

    // SRV heap (for font texture)
    {
        D3D12_DESCRIPTOR_HEAP_DESC desc = {};
        desc.Type           = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
        desc.NumDescriptors = 1;
        desc.Flags          = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
        if (r->device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&r->srv_heap)) != S_OK)
            return false;
    }

    return true;
}

static B32 create_frame_resources(Renderer *r) {
    for (S32 i = 0; i < r->frame_count; i++) {
        if (r->device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
                IID_PPV_ARGS(&r->frames[i].command_allocator)) != S_OK)
            return false;
        r->frames[i].fence_value = 0;
    }

    if (r->device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
            r->frames[0].command_allocator, nullptr, IID_PPV_ARGS(&r->command_list)) != S_OK)
        return false;
    if (r->command_list->Close() != S_OK)
        return false;

    if (r->device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&r->fence)) != S_OK)
        return false;

    r->fence_event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
    return r->fence_event != nullptr;
}

static B32 create_swap_chain(Renderer *r) {
    DXGI_SWAP_CHAIN_DESC1 sd = {};
    sd.BufferCount        = NUM_BACK_BUFFERS;
    sd.Width              = 0;
    sd.Height             = 0;
    sd.Format             = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.Flags              = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
    sd.BufferUsage        = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.SampleDesc.Count   = 1;
    sd.SampleDesc.Quality = 0;
    sd.SwapEffect         = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    sd.AlphaMode          = DXGI_ALPHA_MODE_UNSPECIFIED;
    sd.Scaling            = DXGI_SCALING_STRETCH;
    sd.Stereo             = FALSE;

    IDXGIFactory5 *factory = nullptr;
    if (CreateDXGIFactory1(IID_PPV_ARGS(&factory)) != S_OK)
        return false;

    BOOL allow_tearing = FALSE;
    factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing, sizeof(allow_tearing));
    r->tearing_support = (allow_tearing == TRUE);
    if (r->tearing_support)
        sd.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;

    IDXGISwapChain1 *swap_chain1 = nullptr;
    if (factory->CreateSwapChainForHwnd(r->command_queue, r->hwnd, &sd, nullptr, nullptr, &swap_chain1) != S_OK) {
        factory->Release();
        return false;
    }

    if (swap_chain1->QueryInterface(IID_PPV_ARGS(&r->swap_chain)) != S_OK) {
        swap_chain1->Release();
        factory->Release();
        return false;
    }

    if (r->tearing_support)
        factory->MakeWindowAssociation(r->hwnd, DXGI_MWA_NO_ALT_ENTER);

    swap_chain1->Release();
    factory->Release();

    r->swap_chain->SetMaximumFrameLatency(NUM_BACK_BUFFERS);
    r->swap_chain_waitable = r->swap_chain->GetFrameLatencyWaitableObject();
    return true;
}

static void create_render_targets(Renderer *r) {
    for (UINT i = 0; i < NUM_BACK_BUFFERS; i++) {
        ID3D12Resource *back_buffer = nullptr;
        r->swap_chain->GetBuffer(i, IID_PPV_ARGS(&back_buffer));
        r->device->CreateRenderTargetView(back_buffer, nullptr, r->rtv_descriptors[i]);
        r->render_targets[i] = back_buffer;
    }
}

static void cleanup_render_targets(Renderer *r) {
    for (UINT i = 0; i < NUM_BACK_BUFFERS; i++) {
        if (r->render_targets[i]) {
            r->render_targets[i]->Release();
            r->render_targets[i] = nullptr;
        }
    }
}

static void wait_for_pending(Renderer *r) {
    r->command_queue->Signal(r->fence, ++r->fence_last_signaled);
    r->fence->SetEventOnCompletion(r->fence_last_signaled, r->fence_event);
    WaitForSingleObject(r->fence_event, INFINITE);
}

static FrameContext *wait_for_next_frame(Renderer *r) {
    FrameContext *fc = &r->frames[r->frame_index % r->frame_count];
    if (r->fence->GetCompletedValue() < fc->fence_value) {
        r->fence->SetEventOnCompletion(fc->fence_value, r->fence_event);
        HANDLE waitables[] = { r->swap_chain_waitable, r->fence_event };
        WaitForMultipleObjects(2, waitables, TRUE, INFINITE);
    } else {
        WaitForSingleObject(r->swap_chain_waitable, INFINITE);
    }
    return fc;
}

////////////////////////////////
// Font atlas (FreeType)

static void init_freetype(Renderer *r) {
    FT_Init_FreeType(&r->ft_lib);
    FT_New_Memory_Face(r->ft_lib, font_inter_data, font_inter_size, 0, &r->ft_face);
}

static B32 create_font_atlas(Renderer *r, F32 font_size) {
    S32 pixel_size = (S32)(font_size + 0.5f);
    r->font_atlas_size = font_size;

    FT_Set_Pixel_Sizes(r->ft_face, 0, pixel_size);
    r->font_line_height = (F32)(r->ft_face->size->metrics.height >> 6);

    U8 *atlas_data = (U8 *)calloc(1, FONT_ATLAS_W * FONT_ATLAS_H);

    S32 pen_x = 1, pen_y = 1;
    S32 row_height = 0;
    F32 ascender = (F32)(r->ft_face->size->metrics.ascender >> 6);

    for (S32 i = 0; i < GLYPH_COUNT; i++) {
        char ch = (char)(GLYPH_FIRST + i);
        if (FT_Load_Char(r->ft_face, ch, FT_LOAD_RENDER)) continue;

        FT_GlyphSlot g = r->ft_face->glyph;
        S32 bw = (S32)g->bitmap.width;
        S32 bh = (S32)g->bitmap.rows;
        S32 pad = 2;
        S32 cell_w = bw + pad;
        S32 cell_h = (S32)r->font_line_height + pad;

        if (pen_x + cell_w >= FONT_ATLAS_W) {
            pen_x = 1;
            pen_y += row_height + 1;
            row_height = 0;
        }
        if (pen_y + cell_h >= FONT_ATLAS_H) break;

        // Copy glyph bitmap into atlas at baseline-relative position
        S32 y_off = (S32)ascender - g->bitmap_top;
        for (S32 y = 0; y < bh; y++) {
            S32 dst_y = pen_y + y_off + y;
            if (dst_y < 0 || dst_y >= FONT_ATLAS_H) continue;
            for (S32 x = 0; x < bw; x++) {
                S32 dst_x = pen_x + g->bitmap_left + x;
                if (dst_x < 0 || dst_x >= FONT_ATLAS_W) continue;
                atlas_data[dst_y * FONT_ATLAS_W + dst_x] = g->bitmap.buffer[y * g->bitmap.pitch + x];
            }
        }

        r->glyphs[i].u0 = (F32)pen_x / (F32)FONT_ATLAS_W;
        r->glyphs[i].v0 = (F32)pen_y / (F32)FONT_ATLAS_H;
        r->glyphs[i].u1 = (F32)(pen_x + cell_w) / (F32)FONT_ATLAS_W;
        r->glyphs[i].v1 = (F32)(pen_y + cell_h) / (F32)FONT_ATLAS_H;
        r->glyphs[i].w = (F32)cell_w;
        r->glyphs[i].h = (F32)cell_h;
        r->glyphs[i].x_advance = (F32)(g->advance.x >> 6);

        if (cell_h > row_height) row_height = cell_h;
        pen_x += cell_w + 1;
    }

    // Create D3D12 texture
    D3D12_HEAP_PROPERTIES heap_props = {};
    heap_props.Type = D3D12_HEAP_TYPE_DEFAULT;

    D3D12_RESOURCE_DESC tex_desc = {};
    tex_desc.Dimension        = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
    tex_desc.Width            = FONT_ATLAS_W;
    tex_desc.Height           = FONT_ATLAS_H;
    tex_desc.DepthOrArraySize = 1;
    tex_desc.MipLevels        = 1;
    tex_desc.Format           = DXGI_FORMAT_R8_UNORM;
    tex_desc.SampleDesc.Count = 1;
    tex_desc.Layout           = D3D12_TEXTURE_LAYOUT_UNKNOWN;

    if (r->device->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_NONE,
            &tex_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
            IID_PPV_ARGS(&r->font_texture)) != S_OK) {
        free(atlas_data);
        return false;
    }

    // Upload via staging buffer
    D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
    UINT64 total_bytes = 0;
    r->device->GetCopyableFootprints(&tex_desc, 0, 1, 0, &footprint, nullptr, nullptr, &total_bytes);

    D3D12_HEAP_PROPERTIES upload_heap = {};
    upload_heap.Type = D3D12_HEAP_TYPE_UPLOAD;

    D3D12_RESOURCE_DESC upload_desc = {};
    upload_desc.Dimension        = D3D12_RESOURCE_DIMENSION_BUFFER;
    upload_desc.Width            = total_bytes;
    upload_desc.Height           = 1;
    upload_desc.DepthOrArraySize = 1;
    upload_desc.MipLevels        = 1;
    upload_desc.SampleDesc.Count = 1;
    upload_desc.Layout           = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;

    ID3D12Resource *upload_buf = nullptr;
    r->device->CreateCommittedResource(&upload_heap, D3D12_HEAP_FLAG_NONE,
        &upload_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
        IID_PPV_ARGS(&upload_buf));

    // Copy atlas data into upload buffer
    void *mapped = nullptr;
    D3D12_RANGE read_range = {0, 0};
    upload_buf->Map(0, &read_range, &mapped);
    U8 *dst = (U8 *)mapped;
    for (S32 y = 0; y < FONT_ATLAS_H; y++) {
        memcpy(dst + y * footprint.Footprint.RowPitch,
               atlas_data + y * FONT_ATLAS_W,
               FONT_ATLAS_W);
    }
    upload_buf->Unmap(0, nullptr);

    // Record copy command
    r->frames[0].command_allocator->Reset();
    r->command_list->Reset(r->frames[0].command_allocator, nullptr);

    D3D12_TEXTURE_COPY_LOCATION src_loc = {};
    src_loc.pResource = upload_buf;
    src_loc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
    src_loc.PlacedFootprint = footprint;

    D3D12_TEXTURE_COPY_LOCATION dst_loc = {};
    dst_loc.pResource = r->font_texture;
    dst_loc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
    dst_loc.SubresourceIndex = 0;

    r->command_list->CopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc, nullptr);

    // Transition to shader resource
    D3D12_RESOURCE_BARRIER barrier = {};
    barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    barrier.Transition.pResource = r->font_texture;
    barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
    barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
    barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    r->command_list->ResourceBarrier(1, &barrier);

    r->command_list->Close();
    r->command_queue->ExecuteCommandLists(1, (ID3D12CommandList *const *)&r->command_list);
    wait_for_pending(r);

    upload_buf->Release();
    free(atlas_data);

    // Create SRV
    D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {};
    srv_desc.Format = DXGI_FORMAT_R8_UNORM;
    srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
    srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
    srv_desc.Texture2D.MipLevels = 1;

    r->device->CreateShaderResourceView(r->font_texture,
        &srv_desc, r->srv_heap->GetCPUDescriptorHandleForHeapStart());

    return true;
}

////////////////////////////////
// UI rendering pipeline setup

static B32 create_ui_pipeline(Renderer *r) {
    ID3DBlob *vs_blob = nullptr;
    ID3DBlob *ps_blob = nullptr;
    ID3DBlob *error_blob = nullptr;

    UINT compile_flags = 0;
#ifdef _DEBUG
    compile_flags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

    HRESULT hr = D3DCompile(g_shader_hlsl, strlen(g_shader_hlsl), "ui_shader",
        nullptr, nullptr, "VSMain", "vs_5_0", compile_flags, 0, &vs_blob, &error_blob);
    if (FAILED(hr)) {
        if (error_blob) {
            OutputDebugStringA((char *)error_blob->GetBufferPointer());
            error_blob->Release();
        }
        return false;
    }

    hr = D3DCompile(g_shader_hlsl, strlen(g_shader_hlsl), "ui_shader",
        nullptr, nullptr, "PSMain", "ps_5_0", compile_flags, 0, &ps_blob, &error_blob);
    if (FAILED(hr)) {
        if (error_blob) {
            OutputDebugStringA((char *)error_blob->GetBufferPointer());
            error_blob->Release();
        }
        vs_blob->Release();
        return false;
    }

    // Root signature: root constants + descriptor table for font texture + static sampler
    D3D12_ROOT_PARAMETER root_params[2] = {};

    // [0] Root constants: viewport_size
    root_params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
    root_params[0].Constants.ShaderRegister = 0;
    root_params[0].Constants.RegisterSpace = 0;
    root_params[0].Constants.Num32BitValues = 4;
    root_params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;

    // [1] Descriptor table: font texture SRV
    D3D12_DESCRIPTOR_RANGE srv_range = {};
    srv_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
    srv_range.NumDescriptors = 1;
    srv_range.BaseShaderRegister = 0;
    srv_range.RegisterSpace = 0;
    srv_range.OffsetInDescriptorsFromTableStart = 0;

    root_params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
    root_params[1].DescriptorTable.NumDescriptorRanges = 1;
    root_params[1].DescriptorTable.pDescriptorRanges = &srv_range;
    root_params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;

    // Static sampler for font texture
    D3D12_STATIC_SAMPLER_DESC sampler = {};
    sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
    sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
    sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
    sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
    sampler.ShaderRegister = 0;
    sampler.RegisterSpace = 0;
    sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;

    D3D12_ROOT_SIGNATURE_DESC rs_desc = {};
    rs_desc.NumParameters = 2;
    rs_desc.pParameters = root_params;
    rs_desc.NumStaticSamplers = 1;
    rs_desc.pStaticSamplers = &sampler;
    rs_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;

    ID3DBlob *signature_blob = nullptr;
    if (D3D12SerializeRootSignature(&rs_desc, D3D_ROOT_SIGNATURE_VERSION_1, &signature_blob, &error_blob) != S_OK) {
        if (error_blob) error_blob->Release();
        vs_blob->Release();
        ps_blob->Release();
        return false;
    }

    hr = r->device->CreateRootSignature(0, signature_blob->GetBufferPointer(),
        signature_blob->GetBufferSize(), IID_PPV_ARGS(&r->root_signature));
    signature_blob->Release();
    if (FAILED(hr)) {
        vs_blob->Release();
        ps_blob->Release();
        return false;
    }

    // Input layout
    D3D12_INPUT_ELEMENT_DESC input_layout[] = {
        { "POSITION",  0, DXGI_FORMAT_R32G32_FLOAT,       0, offsetof(UIVertex, pos),              D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "TEXCOORD",  0, DXGI_FORMAT_R32G32_FLOAT,       0, offsetof(UIVertex, uv),               D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "COLOR",     0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, offsetof(UIVertex, col),              D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "TEXCOORD",  1, DXGI_FORMAT_R32G32_FLOAT,       0, offsetof(UIVertex, rect_min),         D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "TEXCOORD",  2, DXGI_FORMAT_R32G32_FLOAT,       0, offsetof(UIVertex, rect_max),         D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "TEXCOORD",  3, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, offsetof(UIVertex, corner_radii),     D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "TEXCOORD",  4, DXGI_FORMAT_R32_FLOAT,          0, offsetof(UIVertex, border_thickness), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "TEXCOORD",  5, DXGI_FORMAT_R32_FLOAT,          0, offsetof(UIVertex, softness),         D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "TEXCOORD",  6, DXGI_FORMAT_R32_FLOAT,          0, offsetof(UIVertex, mode),             D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    };

    D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = {};
    pso_desc.InputLayout = { input_layout, _countof(input_layout) };
    pso_desc.pRootSignature = r->root_signature;
    pso_desc.VS = { vs_blob->GetBufferPointer(), vs_blob->GetBufferSize() };
    pso_desc.PS = { ps_blob->GetBufferPointer(), ps_blob->GetBufferSize() };

    pso_desc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
    pso_desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
    pso_desc.RasterizerState.DepthClipEnable = TRUE;

    D3D12_RENDER_TARGET_BLEND_DESC rt_blend = {};
    rt_blend.BlendEnable = TRUE;
    rt_blend.SrcBlend = D3D12_BLEND_SRC_ALPHA;
    rt_blend.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
    rt_blend.BlendOp = D3D12_BLEND_OP_ADD;
    rt_blend.SrcBlendAlpha = D3D12_BLEND_ONE;
    rt_blend.DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA;
    rt_blend.BlendOpAlpha = D3D12_BLEND_OP_ADD;
    rt_blend.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
    pso_desc.BlendState.RenderTarget[0] = rt_blend;

    pso_desc.DepthStencilState.DepthEnable = FALSE;
    pso_desc.DepthStencilState.StencilEnable = FALSE;
    pso_desc.SampleMask = UINT_MAX;
    pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
    pso_desc.NumRenderTargets = 1;
    pso_desc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
    pso_desc.SampleDesc.Count = 1;

    hr = r->device->CreateGraphicsPipelineState(&pso_desc, IID_PPV_ARGS(&r->pipeline_state));

    vs_blob->Release();
    ps_blob->Release();

    return SUCCEEDED(hr);
}

static B32 create_ui_buffers(Renderer *r) {
    D3D12_HEAP_PROPERTIES heap_props = {};
    heap_props.Type = D3D12_HEAP_TYPE_UPLOAD;

    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        D3D12_RESOURCE_DESC buf_desc = {};
        buf_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
        buf_desc.Width = MAX_VERTICES * sizeof(UIVertex);
        buf_desc.Height = 1;
        buf_desc.DepthOrArraySize = 1;
        buf_desc.MipLevels = 1;
        buf_desc.SampleDesc.Count = 1;
        buf_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;

        if (r->device->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_NONE,
                &buf_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
                IID_PPV_ARGS(&r->vertex_buffers[i])) != S_OK)
            return false;

        D3D12_RANGE read_range = {0, 0};
        r->vertex_buffers[i]->Map(0, &read_range, &r->vb_mapped[i]);
    }

    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        D3D12_RESOURCE_DESC buf_desc = {};
        buf_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
        buf_desc.Width = MAX_INDICES * sizeof(U32);
        buf_desc.Height = 1;
        buf_desc.DepthOrArraySize = 1;
        buf_desc.MipLevels = 1;
        buf_desc.SampleDesc.Count = 1;
        buf_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;

        if (r->device->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_NONE,
                &buf_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
                IID_PPV_ARGS(&r->index_buffers[i])) != S_OK)
            return false;

        D3D12_RANGE read_range = {0, 0};
        r->index_buffers[i]->Map(0, &read_range, &r->ib_mapped[i]);
    }

    return true;
}

////////////////////////////////
// Text measurement callback for UI system

Vec2F32 renderer_measure_text(const char *text, S32 length, F32 font_size, void *user_data) {
    Renderer *r = (Renderer *)user_data;
    if (!r || length == 0) return v2f32(0, font_size);

    FT_Set_Pixel_Sizes(r->ft_face, 0, (FT_UInt)(font_size + 0.5f));

    F32 width = 0;
    for (S32 i = 0; i < length; i++) {
        if (FT_Load_Char(r->ft_face, (FT_ULong)(unsigned char)text[i], FT_LOAD_DEFAULT)) continue;
        width += (F32)(r->ft_face->glyph->advance.x >> 6);
    }
    F32 height = (F32)(r->ft_face->size->metrics.height >> 6);
    return v2f32(width, height);
}

////////////////////////////////
// Quad emission helpers

struct DrawBatch {
    UIVertex *vertices;
    U32      *indices;
    U32       vertex_count;
    U32       index_count;
};

static void emit_quad(DrawBatch *batch,
    F32 x0, F32 y0, F32 x1, F32 y1,
    F32 u0, F32 v0, F32 u1, F32 v1,
    F32 cr, F32 cg, F32 cb, F32 ca,
    F32 rmin_x, F32 rmin_y, F32 rmax_x, F32 rmax_y,
    F32 cr_tl, F32 cr_tr, F32 cr_br, F32 cr_bl,
    F32 border_thickness, F32 softness, F32 mode)
{
    if (batch->vertex_count + 4 > MAX_VERTICES || batch->index_count + 6 > MAX_INDICES)
        return;

    U32 base = batch->vertex_count;
    UIVertex *v = &batch->vertices[base];

    // For SDF mode, expand quad slightly for anti-aliasing
    F32 px0 = x0, py0 = y0, px1 = x1, py1 = y1;
    if (mode < 0.5f) {
        F32 pad = softness + 1.0f;
        px0 -= pad; py0 -= pad; px1 += pad; py1 += pad;
    }

    v[0].pos[0] = px0; v[0].pos[1] = py0; v[0].uv[0] = u0; v[0].uv[1] = v0;
    v[1].pos[0] = px1; v[1].pos[1] = py0; v[1].uv[0] = u1; v[1].uv[1] = v0;
    v[2].pos[0] = px1; v[2].pos[1] = py1; v[2].uv[0] = u1; v[2].uv[1] = v1;
    v[3].pos[0] = px0; v[3].pos[1] = py1; v[3].uv[0] = u0; v[3].uv[1] = v1;

    for (S32 i = 0; i < 4; i++) {
        v[i].col[0] = cr; v[i].col[1] = cg; v[i].col[2] = cb; v[i].col[3] = ca;
        v[i].rect_min[0] = rmin_x; v[i].rect_min[1] = rmin_y;
        v[i].rect_max[0] = rmax_x; v[i].rect_max[1] = rmax_y;
        v[i].corner_radii[0] = cr_tl; v[i].corner_radii[1] = cr_tr;
        v[i].corner_radii[2] = cr_br; v[i].corner_radii[3] = cr_bl;
        v[i].border_thickness = border_thickness;
        v[i].softness = softness;
        v[i].mode = mode;
    }

    U32 *idx = &batch->indices[batch->index_count];
    idx[0] = base; idx[1] = base + 1; idx[2] = base + 2;
    idx[3] = base; idx[4] = base + 2; idx[5] = base + 3;

    batch->vertex_count += 4;
    batch->index_count += 6;
}

static void emit_quad_rotated(DrawBatch *batch,
    F32 x0, F32 y0, F32 x1, F32 y1,
    F32 u0, F32 v0, F32 u1, F32 v1,
    F32 cr, F32 cg, F32 cb, F32 ca,
    F32 angle_rad)
{
    if (batch->vertex_count + 4 > MAX_VERTICES || batch->index_count + 6 > MAX_INDICES)
        return;

    U32 base = batch->vertex_count;
    UIVertex *v = &batch->vertices[base];

    F32 cx = (x0 + x1) * 0.5f;
    F32 cy = (y0 + y1) * 0.5f;
    F32 cosA = cosf(angle_rad);
    F32 sinA = sinf(angle_rad);

    F32 dx0 = x0 - cx, dy0 = y0 - cy;
    F32 dx1 = x1 - cx, dy1 = y1 - cy;

    v[0].pos[0] = cx + dx0 * cosA - dy0 * sinA;
    v[0].pos[1] = cy + dx0 * sinA + dy0 * cosA;
    v[0].uv[0] = u0; v[0].uv[1] = v0;

    v[1].pos[0] = cx + dx1 * cosA - dy0 * sinA;
    v[1].pos[1] = cy + dx1 * sinA + dy0 * cosA;
    v[1].uv[0] = u1; v[1].uv[1] = v0;

    v[2].pos[0] = cx + dx1 * cosA - dy1 * sinA;
    v[2].pos[1] = cy + dx1 * sinA + dy1 * cosA;
    v[2].uv[0] = u1; v[2].uv[1] = v1;

    v[3].pos[0] = cx + dx0 * cosA - dy1 * sinA;
    v[3].pos[1] = cy + dx0 * sinA + dy1 * cosA;
    v[3].uv[0] = u0; v[3].uv[1] = v1;

    for (S32 i = 0; i < 4; i++) {
        v[i].col[0] = cr; v[i].col[1] = cg; v[i].col[2] = cb; v[i].col[3] = ca;
        v[i].rect_min[0] = 0; v[i].rect_min[1] = 0;
        v[i].rect_max[0] = 0; v[i].rect_max[1] = 0;
        v[i].corner_radii[0] = 0; v[i].corner_radii[1] = 0;
        v[i].corner_radii[2] = 0; v[i].corner_radii[3] = 0;
        v[i].border_thickness = 0;
        v[i].softness = 0;
        v[i].mode = 2.0f;
    }

    U32 *idx = &batch->indices[batch->index_count];
    idx[0] = base; idx[1] = base + 1; idx[2] = base + 2;
    idx[3] = base; idx[4] = base + 2; idx[5] = base + 3;

    batch->vertex_count += 4;
    batch->index_count += 6;
}

static void emit_rect(DrawBatch *batch,
    F32 x0, F32 y0, F32 x1, F32 y1,
    F32 cr, F32 cg, F32 cb, F32 ca,
    F32 cr_tl, F32 cr_tr, F32 cr_br, F32 cr_bl,
    F32 border_thickness, F32 softness)
{
    emit_quad(batch, x0, y0, x1, y1,
        0, 0, 0, 0,
        cr, cg, cb, ca,
        x0, y0, x1, y1,
        cr_tl, cr_tr, cr_br, cr_bl,
        border_thickness, softness, 0.0f);
}

static void emit_rect_vgradient(DrawBatch *batch,
    F32 x0, F32 y0, F32 x1, F32 y1,
    F32 tr, F32 tg, F32 tb, F32 ta,
    F32 br, F32 bg, F32 bb_, F32 ba,
    F32 cr_tl, F32 cr_tr, F32 cr_br, F32 cr_bl,
    F32 softness)
{
    if (batch->vertex_count + 4 > MAX_VERTICES || batch->index_count + 6 > MAX_INDICES)
        return;

    U32 base = batch->vertex_count;
    UIVertex *v = &batch->vertices[base];

    F32 pad = softness + 1.0f;
    F32 px0 = x0 - pad, py0 = y0 - pad, px1 = x1 + pad, py1 = y1 + pad;

    v[0].pos[0] = px0; v[0].pos[1] = py0; v[0].uv[0] = 0; v[0].uv[1] = 0;
    v[1].pos[0] = px1; v[1].pos[1] = py0; v[1].uv[0] = 0; v[1].uv[1] = 0;
    v[2].pos[0] = px1; v[2].pos[1] = py1; v[2].uv[0] = 0; v[2].uv[1] = 0;
    v[3].pos[0] = px0; v[3].pos[1] = py1; v[3].uv[0] = 0; v[3].uv[1] = 0;

    // Top vertices get top color, bottom vertices get bottom color
    v[0].col[0] = tr; v[0].col[1] = tg; v[0].col[2] = tb; v[0].col[3] = ta;
    v[1].col[0] = tr; v[1].col[1] = tg; v[1].col[2] = tb; v[1].col[3] = ta;
    v[2].col[0] = br; v[2].col[1] = bg; v[2].col[2] = bb_; v[2].col[3] = ba;
    v[3].col[0] = br; v[3].col[1] = bg; v[3].col[2] = bb_; v[3].col[3] = ba;

    for (S32 i = 0; i < 4; i++) {
        v[i].rect_min[0] = x0; v[i].rect_min[1] = y0;
        v[i].rect_max[0] = x1; v[i].rect_max[1] = y1;
        v[i].corner_radii[0] = cr_tl; v[i].corner_radii[1] = cr_tr;
        v[i].corner_radii[2] = cr_br; v[i].corner_radii[3] = cr_bl;
        v[i].border_thickness = 0;
        v[i].softness = softness;
        v[i].mode = 0;
    }

    U32 *idx = &batch->indices[batch->index_count];
    idx[0] = base; idx[1] = base + 1; idx[2] = base + 2;
    idx[3] = base; idx[4] = base + 2; idx[5] = base + 3;

    batch->vertex_count += 4;
    batch->index_count += 6;
}

static void emit_text_glyphs(DrawBatch *batch, Renderer *r,
    Clay_BoundingBox bbox, Clay_Color color, const char *text, S32 text_len,
    U16 font_size)
{
    if (text_len == 0 || color.a < 0.1f) return;

    // Color is 0-255 in Clay convention, normalize to 0-1
    F32 cr = color.r / 255.f;
    F32 cg = color.g / 255.f;
    F32 cb = color.b / 255.f;
    F32 ca = color.a / 255.f;

    F32 scale = (F32)font_size / r->font_atlas_size;
    F32 text_h = r->font_line_height * scale;

    // Vertically center text in bounding box, snapped to pixel grid to avoid blurry glyphs
    F32 x = floorf(bbox.x + 0.5f);
    F32 y = floorf(bbox.y + (bbox.height - text_h) * 0.5f + 0.5f);

    for (S32 i = 0; i < text_len; i++) {
        char ch = text[i];
        if (ch < GLYPH_FIRST || ch > GLYPH_LAST) {
            if (ch == ' ') {
                S32 gi = ' ' - GLYPH_FIRST;
                if (gi >= 0 && gi < GLYPH_COUNT)
                    x += r->glyphs[gi].x_advance * scale;
                continue;
            }
            ch = '?';
        }
        S32 gi = ch - GLYPH_FIRST;
        if (gi < 0 || gi >= GLYPH_COUNT) continue;

        GlyphInfo *g = &r->glyphs[gi];
        F32 gw = g->w * scale;
        F32 gh = g->h * scale;

        emit_quad(batch,
            x, y, x + gw, y + gh,
            g->u0, g->v0, g->u1, g->v1,
            cr, cg, cb, ca,
            0, 0, 0, 0,
            0, 0, 0, 0,
            0, 0, 1.0f);

        x += g->x_advance * scale;
    }
}

////////////////////////////////
// Flush helper: issues a draw call for accumulated vertices, then resets batch

static void flush_batch(Renderer *r, DrawBatch *batch, UINT buf_idx, U32 *flush_index_start, ID3D12DescriptorHeap *tex_heap = nullptr) {
    U32 draw_index_count = batch->index_count - *flush_index_start;
    if (draw_index_count == 0) return;

    r->command_list->SetPipelineState(r->pipeline_state);
    r->command_list->SetGraphicsRootSignature(r->root_signature);

    F32 constants[4] = { (F32)r->width, (F32)r->height, 0, 0 };
    r->command_list->SetGraphicsRoot32BitConstants(0, 4, constants, 0);

    // Bind texture (font or icon)
    ID3D12DescriptorHeap *heap = tex_heap ? tex_heap : r->srv_heap;
    r->command_list->SetDescriptorHeaps(1, &heap);
    r->command_list->SetGraphicsRootDescriptorTable(1,
        heap->GetGPUDescriptorHandleForHeapStart());

    r->command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    D3D12_VERTEX_BUFFER_VIEW vbv = {};
    vbv.BufferLocation = r->vertex_buffers[buf_idx]->GetGPUVirtualAddress();
    vbv.SizeInBytes = batch->vertex_count * sizeof(UIVertex);
    vbv.StrideInBytes = sizeof(UIVertex);
    r->command_list->IASetVertexBuffers(0, 1, &vbv);

    D3D12_INDEX_BUFFER_VIEW ibv = {};
    ibv.BufferLocation = r->index_buffers[buf_idx]->GetGPUVirtualAddress();
    ibv.SizeInBytes = batch->index_count * sizeof(U32);
    ibv.Format = DXGI_FORMAT_R32_UINT;
    r->command_list->IASetIndexBuffer(&ibv);

    r->command_list->DrawIndexedInstanced(draw_index_count, 1, *flush_index_start, 0, 0);

    *flush_index_start = batch->index_count;
}

////////////////////////////////
// Public API

Renderer *renderer_create(RendererDesc *desc) {
    Renderer *r = new Renderer();
    memset(r, 0, sizeof(*r));

    r->hwnd        = (HWND)desc->window_handle;
    r->width       = desc->width;
    r->height      = desc->height;
    r->frame_count = desc->frame_count;
    if (r->frame_count > NUM_BACK_BUFFERS) r->frame_count = NUM_BACK_BUFFERS;

    if (!create_device(r))            goto fail;
    if (!create_command_queue(r))     goto fail;
    if (!create_descriptor_heaps(r))  goto fail;
    if (!create_frame_resources(r))   goto fail;
    if (!create_swap_chain(r))        goto fail;
    create_render_targets(r);

    if (!create_ui_pipeline(r))       goto fail;
    if (!create_ui_buffers(r))        goto fail;

    init_freetype(r);
    if (!create_font_atlas(r, 22.0f)) goto fail;

    return r;

fail:
    renderer_destroy(r);
    return nullptr;
}

void renderer_destroy(Renderer *r) {
    if (!r) return;

    wait_for_pending(r);

    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        if (r->vertex_buffers[i]) r->vertex_buffers[i]->Release();
        if (r->index_buffers[i])  r->index_buffers[i]->Release();
    }
    if (r->font_texture)     r->font_texture->Release();
    if (r->icon_texture)     r->icon_texture->Release();
    if (r->icon_srv_heap)    r->icon_srv_heap->Release();
    if (r->pipeline_state)   r->pipeline_state->Release();
    if (r->root_signature)   r->root_signature->Release();

    if (r->ft_face) FT_Done_Face(r->ft_face);
    if (r->ft_lib)  FT_Done_FreeType(r->ft_lib);

    cleanup_render_targets(r);

    if (r->swap_chain)       { r->swap_chain->SetFullscreenState(false, nullptr); r->swap_chain->Release(); }
    if (r->swap_chain_waitable) CloseHandle(r->swap_chain_waitable);
    for (S32 i = 0; i < r->frame_count; i++)
        if (r->frames[i].command_allocator) r->frames[i].command_allocator->Release();
    if (r->command_queue)    r->command_queue->Release();
    if (r->command_list)     r->command_list->Release();
    if (r->rtv_heap)         r->rtv_heap->Release();
    if (r->srv_heap)         r->srv_heap->Release();
    if (r->fence)            r->fence->Release();
    if (r->fence_event)      CloseHandle(r->fence_event);
    if (r->device)           r->device->Release();

#ifdef DX12_ENABLE_DEBUG_LAYER
    IDXGIDebug1 *dxgi_debug = nullptr;
    if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(&dxgi_debug)))) {
        dxgi_debug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_SUMMARY);
        dxgi_debug->Release();
    }
#endif

    delete r;
}

B32 renderer_begin_frame(Renderer *r) {
    if ((r->swap_chain_occluded && r->swap_chain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED)
        || IsIconic(r->hwnd))
    {
        Sleep(10);
        return false;
    }
    r->swap_chain_occluded = false;
    return true;
}

void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
    FrameContext *fc = wait_for_next_frame(r);
    UINT back_buffer_idx = r->swap_chain->GetCurrentBackBufferIndex();
    UINT buf_idx = r->frame_index % NUM_BACK_BUFFERS;

    fc->command_allocator->Reset();
    r->command_list->Reset(fc->command_allocator, nullptr);

    D3D12_RESOURCE_BARRIER barrier = {};
    barrier.Type                   = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    barrier.Flags                  = D3D12_RESOURCE_BARRIER_FLAG_NONE;
    barrier.Transition.pResource   = r->render_targets[back_buffer_idx];
    barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
    barrier.Transition.StateAfter  = D3D12_RESOURCE_STATE_RENDER_TARGET;
    r->command_list->ResourceBarrier(1, &barrier);

    const F32 clear_color[4] = { r->clear_r, r->clear_g, r->clear_b, 1.0f };
    r->command_list->ClearRenderTargetView(r->rtv_descriptors[back_buffer_idx], clear_color, 0, nullptr);
    r->command_list->OMSetRenderTargets(1, &r->rtv_descriptors[back_buffer_idx], FALSE, nullptr);

    D3D12_VIEWPORT viewport = {};
    viewport.Width = (FLOAT)r->width;
    viewport.Height = (FLOAT)r->height;
    viewport.MaxDepth = 1.0f;
    r->command_list->RSSetViewports(1, &viewport);

    D3D12_RECT scissor = { 0, 0, (LONG)r->width, (LONG)r->height };
    r->command_list->RSSetScissorRects(1, &scissor);

    // Process Clay render commands
    if (render_commands.length > 0) {
        DrawBatch batch = {};
        batch.vertices = (UIVertex *)r->vb_mapped[buf_idx];
        batch.indices = (U32 *)r->ib_mapped[buf_idx];
        batch.vertex_count = 0;
        batch.index_count = 0;

        // Track which texture is currently bound (0 = font, 1 = icon)
        S32 bound_texture = 0;
        U32 flush_index_start = 0;

        auto bind_font = [&]() {
            if (bound_texture != 0) {
                ID3D12DescriptorHeap *heap = bound_texture == 1 ? r->icon_srv_heap : r->srv_heap;
                flush_batch(r, &batch, buf_idx, &flush_index_start, heap);
                bound_texture = 0;
            }
        };

        auto bind_icon = [&]() {
            if (bound_texture != 1 && r->icon_srv_heap) {
                flush_batch(r, &batch, buf_idx, &flush_index_start, r->srv_heap);
                bound_texture = 1;
            }
        };

        for (S32 i = 0; i < render_commands.length; i++) {
            Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&render_commands, i);
            Clay_BoundingBox bb = cmd->boundingBox;

            switch (cmd->commandType) {
            case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
                Clay_RectangleRenderData *rect = &cmd->renderData.rectangle;
                Clay_Color c = rect->backgroundColor;
                emit_rect(&batch,
                    bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
                    c.r / 255.f, c.g / 255.f, c.b / 255.f, c.a / 255.f,
                    rect->cornerRadius.topLeft, rect->cornerRadius.topRight,
                    rect->cornerRadius.bottomRight, rect->cornerRadius.bottomLeft,
                    0, 1.0f);
            } break;

            case CLAY_RENDER_COMMAND_TYPE_BORDER: {
                Clay_BorderRenderData *border = &cmd->renderData.border;
                Clay_Color c = border->color;
                F32 cr_norm = c.r / 255.f;
                F32 cg_norm = c.g / 255.f;
                F32 cb_norm = c.b / 255.f;
                F32 ca_norm = c.a / 255.f;

                // Use SDF rounded border when corner radius is present and widths are uniform
                Clay_CornerRadius cr = border->cornerRadius;
                B32 has_radius = cr.topLeft > 0 || cr.topRight > 0 || cr.bottomLeft > 0 || cr.bottomRight > 0;
                B32 uniform = border->width.top == border->width.bottom &&
                               border->width.top == border->width.left &&
                               border->width.top == border->width.right &&
                               border->width.top > 0;

                if (has_radius && uniform) {
                    emit_rect(&batch,
                        bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
                        cr_norm, cg_norm, cb_norm, ca_norm,
                        cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft,
                        (F32)border->width.top, 1.0f);
                } else {
                    if (border->width.top > 0) {
                        emit_rect(&batch, bb.x, bb.y, bb.x + bb.width, bb.y + border->width.top,
                            cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
                    }
                    if (border->width.bottom > 0) {
                        emit_rect(&batch, bb.x, bb.y + bb.height - border->width.bottom, bb.x + bb.width, bb.y + bb.height,
                            cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
                    }
                    if (border->width.left > 0) {
                        emit_rect(&batch, bb.x, bb.y, bb.x + border->width.left, bb.y + bb.height,
                            cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
                    }
                    if (border->width.right > 0) {
                        emit_rect(&batch, bb.x + bb.width - border->width.right, bb.y, bb.x + bb.width, bb.y + bb.height,
                            cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
                    }
                }
            } break;

            case CLAY_RENDER_COMMAND_TYPE_TEXT: {
                bind_font();
                Clay_TextRenderData *text = &cmd->renderData.text;
                emit_text_glyphs(&batch, r, bb, text->textColor,
                    text->stringContents.chars, text->stringContents.length,
                    text->fontSize);
            } break;

            case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
                // Flush before changing scissor
                ID3D12DescriptorHeap *heap = bound_texture == 1 ? r->icon_srv_heap : r->srv_heap;
                flush_batch(r, &batch, buf_idx, &flush_index_start, heap);
                D3D12_RECT clip = {};
                clip.left   = (LONG)Max(bb.x, 0.f);
                clip.top    = (LONG)Max(bb.y, 0.f);
                clip.right  = (LONG)Min(bb.x + bb.width, (F32)r->width);
                clip.bottom = (LONG)Min(bb.y + bb.height, (F32)r->height);
                if (clip.right <= clip.left) clip.right = clip.left + 1;
                if (clip.bottom <= clip.top) clip.bottom = clip.top + 1;
                r->command_list->RSSetScissorRects(1, &clip);
            } break;

            case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
                ID3D12DescriptorHeap *heap = bound_texture == 1 ? r->icon_srv_heap : r->srv_heap;
                flush_batch(r, &batch, buf_idx, &flush_index_start, heap);
                D3D12_RECT full_scissor = { 0, 0, (LONG)r->width, (LONG)r->height };
                r->command_list->RSSetScissorRects(1, &full_scissor);
            } break;

            case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
                Clay_CustomRenderData *custom = &cmd->renderData.custom;
                if (custom->customData) {
                    CustomRenderType type = *(CustomRenderType *)custom->customData;
                    if (type == CUSTOM_RENDER_VGRADIENT) {
                        bind_font();
                        CustomGradientData *grad = (CustomGradientData *)custom->customData;
                        Clay_Color tc = grad->top_color;
                        Clay_Color bc = grad->bottom_color;
                        emit_rect_vgradient(&batch,
                            bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
                            tc.r / 255.f, tc.g / 255.f, tc.b / 255.f, tc.a / 255.f,
                            bc.r / 255.f, bc.g / 255.f, bc.b / 255.f, bc.a / 255.f,
                            custom->cornerRadius.topLeft, custom->cornerRadius.topRight,
                            custom->cornerRadius.bottomRight, custom->cornerRadius.bottomLeft,
                            1.0f);
                    } else if (type == CUSTOM_RENDER_ICON) {
                        bind_icon();
                        CustomIconData *icon = (CustomIconData *)custom->customData;
                        Clay_Color c = icon->color;
                        F32 cr = c.r / 255.f, cg = c.g / 255.f;
                        F32 cb = c.b / 255.f, ca = c.a / 255.f;
                        UI_IconInfo *info = &g_icons[icon->icon_id];
                        emit_quad(&batch,
                            bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
                            info->u0, info->v0, info->u1, info->v1,
                            cr, cg, cb, ca,
                            0, 0, 0, 0,
                            0, 0, 0, 0,
                            0, 0, 2.0f);
                    } else if (type == CUSTOM_RENDER_ROTATED_ICON) {
                        bind_icon();
                        CustomRotatedIconData *ri = (CustomRotatedIconData *)custom->customData;
                        Clay_Color c = ri->color;
                        F32 cr = c.r / 255.f, cg = c.g / 255.f;
                        F32 cb = c.b / 255.f, ca = c.a / 255.f;
                        UI_IconInfo *info = &g_icons[ri->icon_id];
                        emit_quad_rotated(&batch,
                            bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
                            info->u0, info->v0, info->u1, info->v1,
                            cr, cg, cb, ca,
                            ri->angle_rad);
                    }
                }
            } break;

            case CLAY_RENDER_COMMAND_TYPE_IMAGE:
            default:
                break;
            }
        }

        // Flush remaining
        ID3D12DescriptorHeap *heap = bound_texture == 1 ? r->icon_srv_heap : r->srv_heap;
        flush_batch(r, &batch, buf_idx, &flush_index_start, heap);
    }

    barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    barrier.Transition.StateAfter  = D3D12_RESOURCE_STATE_PRESENT;
    r->command_list->ResourceBarrier(1, &barrier);
    r->command_list->Close();

    r->command_queue->ExecuteCommandLists(1, (ID3D12CommandList *const *)&r->command_list);
    r->command_queue->Signal(r->fence, ++r->fence_last_signaled);
    fc->fence_value = r->fence_last_signaled;

    HRESULT hr = r->swap_chain->Present(1, 0);
    r->swap_chain_occluded = (hr == DXGI_STATUS_OCCLUDED);
    r->frame_index++;
}

void renderer_create_icon_atlas(Renderer *r, const U8 *data, S32 w, S32 h) {
    // Create texture resource
    D3D12_HEAP_PROPERTIES heap_props = {};
    heap_props.Type = D3D12_HEAP_TYPE_DEFAULT;

    D3D12_RESOURCE_DESC tex_desc = {};
    tex_desc.Dimension        = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
    tex_desc.Width            = w;
    tex_desc.Height           = h;
    tex_desc.DepthOrArraySize = 1;
    tex_desc.MipLevels        = 1;
    tex_desc.Format           = DXGI_FORMAT_R8G8B8A8_UNORM;
    tex_desc.SampleDesc.Count = 1;
    tex_desc.Layout           = D3D12_TEXTURE_LAYOUT_UNKNOWN;

    r->device->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_NONE,
        &tex_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
        IID_PPV_ARGS(&r->icon_texture));

    // Upload via staging buffer
    D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
    UINT64 total_bytes = 0;
    r->device->GetCopyableFootprints(&tex_desc, 0, 1, 0, &footprint, nullptr, nullptr, &total_bytes);

    D3D12_HEAP_PROPERTIES upload_heap = {};
    upload_heap.Type = D3D12_HEAP_TYPE_UPLOAD;

    D3D12_RESOURCE_DESC upload_desc = {};
    upload_desc.Dimension        = D3D12_RESOURCE_DIMENSION_BUFFER;
    upload_desc.Width            = total_bytes;
    upload_desc.Height           = 1;
    upload_desc.DepthOrArraySize = 1;
    upload_desc.MipLevels        = 1;
    upload_desc.SampleDesc.Count = 1;
    upload_desc.Layout           = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;

    ID3D12Resource *upload_buf = nullptr;
    r->device->CreateCommittedResource(&upload_heap, D3D12_HEAP_FLAG_NONE,
        &upload_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
        IID_PPV_ARGS(&upload_buf));

    void *mapped = nullptr;
    D3D12_RANGE read_range = {0, 0};
    upload_buf->Map(0, &read_range, &mapped);
    U8 *dst = (U8 *)mapped;
    for (S32 y = 0; y < h; y++) {
        memcpy(dst + y * footprint.Footprint.RowPitch, data + y * w * 4, w * 4);
    }
    upload_buf->Unmap(0, nullptr);

    r->frames[0].command_allocator->Reset();
    r->command_list->Reset(r->frames[0].command_allocator, nullptr);

    D3D12_TEXTURE_COPY_LOCATION src_loc = {};
    src_loc.pResource = upload_buf;
    src_loc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
    src_loc.PlacedFootprint = footprint;

    D3D12_TEXTURE_COPY_LOCATION dst_loc = {};
    dst_loc.pResource = r->icon_texture;
    dst_loc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
    dst_loc.SubresourceIndex = 0;

    r->command_list->CopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc, nullptr);

    D3D12_RESOURCE_BARRIER barrier = {};
    barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    barrier.Transition.pResource = r->icon_texture;
    barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
    barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
    barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    r->command_list->ResourceBarrier(1, &barrier);

    r->command_list->Close();
    r->command_queue->ExecuteCommandLists(1, (ID3D12CommandList *const *)&r->command_list);
    wait_for_pending(r);
    upload_buf->Release();

    // Create separate SRV heap for icon texture
    D3D12_DESCRIPTOR_HEAP_DESC srv_desc = {};
    srv_desc.Type           = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
    srv_desc.NumDescriptors = 1;
    srv_desc.Flags          = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
    r->device->CreateDescriptorHeap(&srv_desc, IID_PPV_ARGS(&r->icon_srv_heap));

    D3D12_SHADER_RESOURCE_VIEW_DESC srv_view = {};
    srv_view.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    srv_view.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
    srv_view.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
    srv_view.Texture2D.MipLevels = 1;

    r->device->CreateShaderResourceView(r->icon_texture,
        &srv_view, r->icon_srv_heap->GetCPUDescriptorHandleForHeapStart());
}

void renderer_resize(Renderer *r, S32 width, S32 height) {
    if (width <= 0 || height <= 0) return;

    wait_for_pending(r);
    cleanup_render_targets(r);

    DXGI_SWAP_CHAIN_DESC1 desc = {};
    r->swap_chain->GetDesc1(&desc);
    r->swap_chain->ResizeBuffers(0, (UINT)width, (UINT)height, desc.Format, desc.Flags);

    create_render_targets(r);

    r->width  = width;
    r->height = height;
}

void renderer_set_clear_color(Renderer *r, F32 cr, F32 cg, F32 cb) {
    r->clear_r = cr;
    r->clear_g = cg;
    r->clear_b = cb;
}

void renderer_set_font_scale(Renderer *r, F32 scale) {
    // Build atlas at the largest font size used in the UI (22px clock display)
    // so all smaller sizes scale down (crisper) rather than up (blurry).
    F32 target_size = 22.0f * scale;
    if (fabsf(target_size - r->font_atlas_size) < 0.1f) return;
    wait_for_pending(r);
    if (r->font_texture) { r->font_texture->Release(); r->font_texture = nullptr; }
    create_font_atlas(r, target_size);
}
