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

#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#import <Cocoa/Cocoa.h>
#include <math.h>
#include <stdlib.h>
#include <string.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"

#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 — matches DX12 UIVertex exactly

typedef 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
} UIVertex;

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

typedef struct GlyphInfo {
    F32 u0, v0, u1, v1;
    F32 w, h;
    F32 x_advance;
} GlyphInfo;

////////////////////////////////
// Metal shader (MSL) — port of HLSL SDF shader

static const char *g_shader_msl =
    "#include <metal_stdlib>\n"
    "using namespace metal;\n"
    "\n"
    "struct Vertex {\n"
    "    float2 pos              [[attribute(0)]];\n"
    "    float2 uv               [[attribute(1)]];\n"
    "    float4 col              [[attribute(2)]];\n"
    "    float2 rect_min         [[attribute(3)]];\n"
    "    float2 rect_max         [[attribute(4)]];\n"
    "    float4 corner_radii     [[attribute(5)]];\n"
    "    float  border_thickness [[attribute(6)]];\n"
    "    float  softness         [[attribute(7)]];\n"
    "    float  mode             [[attribute(8)]];\n"
    "};\n"
    "\n"
    "struct Fragment {\n"
    "    float4 pos              [[position]];\n"
    "    float2 uv;\n"
    "    float4 col;\n"
    "    float2 rect_min;\n"
    "    float2 rect_max;\n"
    "    float4 corner_radii;\n"
    "    float  border_thickness;\n"
    "    float  softness;\n"
    "    float  mode;\n"
    "};\n"
    "\n"
    "struct Constants {\n"
    "    float2 viewport_size;\n"
    "};\n"
    "\n"
    "vertex Fragment vertex_main(Vertex in [[stage_in]],\n"
    "                            constant Constants &cb [[buffer(1)]]) {\n"
    "    Fragment out;\n"
    "    float2 ndc;\n"
    "    ndc.x = (in.pos.x / cb.viewport_size.x) * 2.0 - 1.0;\n"
    "    ndc.y = 1.0 - (in.pos.y / cb.viewport_size.y) * 2.0;\n"
    "    out.pos = float4(ndc, 0.0, 1.0);\n"
    "    out.uv = in.uv;\n"
    "    out.col = in.col;\n"
    "    out.rect_min = in.rect_min;\n"
    "    out.rect_max = in.rect_max;\n"
    "    out.corner_radii = in.corner_radii;\n"
    "    out.border_thickness = in.border_thickness;\n"
    "    out.softness = in.softness;\n"
    "    out.mode = in.mode;\n"
    "    return out;\n"
    "}\n"
    "\n"
    "float rounded_rect_sdf(float2 sample_pos, float2 rect_center, float2 rect_half_size, float radius) {\n"
    "    float2 d = abs(sample_pos - rect_center) - rect_half_size + float2(radius, radius);\n"
    "    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;\n"
    "}\n"
    "\n"
    "fragment float4 fragment_main(Fragment in [[stage_in]],\n"
    "                              texture2d<float> font_tex [[texture(0)]],\n"
    "                              sampler font_smp [[sampler(0)]]) {\n"
    "    float4 col = in.col;\n"
    "\n"
    "    if (in.mode > 1.5) {\n"
    "        float4 tex = font_tex.sample(font_smp, in.uv);\n"
    "        col *= tex;\n"
    "    } else if (in.mode > 0.5) {\n"
    "        float alpha = font_tex.sample(font_smp, in.uv).r;\n"
    "        col.a *= alpha;\n"
    "    } else {\n"
    "        float2 pixel_pos = in.pos.xy;\n"
    "        float2 rect_center = (in.rect_min + in.rect_max) * 0.5;\n"
    "        float2 rect_half_size = (in.rect_max - in.rect_min) * 0.5;\n"
    "        float radius = (pixel_pos.x < rect_center.x)\n"
    "            ? ((pixel_pos.y < rect_center.y) ? in.corner_radii.x : in.corner_radii.w)\n"
    "            : ((pixel_pos.y < rect_center.y) ? in.corner_radii.y : in.corner_radii.z);\n"
    "        float softness = max(in.softness, 0.5);\n"
    "        float dist = rounded_rect_sdf(pixel_pos, rect_center, rect_half_size, radius);\n"
    "\n"
    "        if (in.border_thickness > 0) {\n"
    "            float inner_dist = dist + in.border_thickness;\n"
    "            float outer_alpha = 1.0 - smoothstep(-softness, softness, dist);\n"
    "            float inner_alpha = smoothstep(-softness, softness, inner_dist);\n"
    "            col.a *= outer_alpha * inner_alpha;\n"
    "        } else {\n"
    "            col.a *= 1.0 - smoothstep(-softness, softness, dist);\n"
    "        }\n"
    "    }\n"
    "\n"
    "    // Dither to reduce gradient banding (interleaved gradient noise)\n"
    "    float dither = fract(52.9829189 * fract(dot(in.pos.xy, float2(0.06711056, 0.00583715)))) - 0.5;\n"
    "    col.rgb += dither / 255.0;\n"
    "\n"
    "    if (col.a < 0.002) discard_fragment();\n"
    "    return col;\n"
    "}\n";

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

typedef struct Renderer {
    struct Renderer      *parent;  // non-null for shared renderers
    S32               width;
    S32               height;
    S32               frame_count;
    U32              frame_index;
    F32                   backing_scale;

    id<MTLDevice>         device;
    id<MTLCommandQueue>   command_queue;
    CAMetalLayer         *metal_layer;
    id<MTLRenderPipelineState> pipeline_state;

    dispatch_semaphore_t  frame_semaphore;

    // Double-buffered vertex/index buffers
    id<MTLBuffer>         vertex_buffers[NUM_BACK_BUFFERS];
    id<MTLBuffer>         index_buffers[NUM_BACK_BUFFERS];

    // Font atlas
    id<MTLTexture>        font_texture;
    id<MTLSamplerState>   font_sampler;
    GlyphInfo             glyphs[GLYPH_COUNT];
    F32                   font_atlas_size;
    F32                   font_line_height;

    // Icon atlas
    id<MTLTexture>        icon_texture;

    // FreeType
    FT_Library            ft_lib;
    FT_Face               ft_face;

    // Current drawable (acquired in begin_frame)
    id<CAMetalDrawable>   current_drawable;

    // Clear color
    F32                   clear_r, clear_g, clear_b;
} Renderer;

////////////////////////////////
// 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 Metal texture
    MTLTextureDescriptor *tex_desc = [[MTLTextureDescriptor alloc] init];
    tex_desc.pixelFormat = MTLPixelFormatR8Unorm;
    tex_desc.width = FONT_ATLAS_W;
    tex_desc.height = FONT_ATLAS_H;
    tex_desc.usage = MTLTextureUsageShaderRead;

    r->font_texture = [r->device newTextureWithDescriptor:tex_desc];
    [r->font_texture replaceRegion:MTLRegionMake2D(0, 0, FONT_ATLAS_W, FONT_ATLAS_H)
                       mipmapLevel:0
                         withBytes:atlas_data
                       bytesPerRow:FONT_ATLAS_W];

    free(atlas_data);

    // Create sampler
    MTLSamplerDescriptor *samp_desc = [[MTLSamplerDescriptor alloc] init];
    samp_desc.minFilter = MTLSamplerMinMagFilterLinear;
    samp_desc.magFilter = MTLSamplerMinMagFilterLinear;
    samp_desc.sAddressMode = MTLSamplerAddressModeClampToEdge;
    samp_desc.tAddressMode = MTLSamplerAddressModeClampToEdge;
    r->font_sampler = [r->device newSamplerStateWithDescriptor:samp_desc];

    return true;
}

////////////////////////////////
// Text measurement (FreeType)

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);
}

////////////////////////////////
// Draw batch and quad emission (same logic as DX12)

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

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];

    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);

    // Corner offsets from center
    F32 dx0 = x0 - cx, dy0 = y0 - cy;
    F32 dx1 = x1 - cx, dy1 = y1 - cy;

    // Rotate each corner around center
    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; // RGBA textured
    }

    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;

    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;

    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;

    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;
    }
}

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

Renderer *renderer_create(RendererDesc *desc) {
    Renderer *r = (Renderer *)calloc(1, sizeof(Renderer));

    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;

    // Get the NSView and attach a CAMetalLayer
    NSView *view = (__bridge NSView *)desc->window_handle;
    [view setWantsLayer:YES];

    r->device = MTLCreateSystemDefaultDevice();
    if (!r->device) { free(r); return NULL; }

    r->command_queue = [r->device newCommandQueue];

    CAMetalLayer *layer = [CAMetalLayer layer];
    layer.device = r->device;
    layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
    layer.framebufferOnly = YES;
    layer.displaySyncEnabled = YES;
    layer.maximumDrawableCount = 3;

    NSWindow *window = [view window];
    r->backing_scale = (F32)[window backingScaleFactor];
    layer.contentsScale = r->backing_scale;
    layer.drawableSize = CGSizeMake(r->width, r->height);

    [view setLayer:layer];
    r->metal_layer = layer;

    r->frame_semaphore = dispatch_semaphore_create(NUM_BACK_BUFFERS);

    // Compile shaders
    NSError *error = nil;
    id<MTLLibrary> library = [r->device newLibraryWithSource:
        [NSString stringWithUTF8String:g_shader_msl] options:nil error:&error];
    if (!library) {
        NSLog(@"Metal shader compile error: %@", error);
        free(r);
        return NULL;
    }

    id<MTLFunction> vert_fn = [library newFunctionWithName:@"vertex_main"];
    id<MTLFunction> frag_fn = [library newFunctionWithName:@"fragment_main"];

    // Vertex descriptor
    MTLVertexDescriptor *vtx_desc = [[MTLVertexDescriptor alloc] init];
    vtx_desc.attributes[0].format = MTLVertexFormatFloat2;
    vtx_desc.attributes[0].offset = offsetof(UIVertex, pos);
    vtx_desc.attributes[0].bufferIndex = 0;
    vtx_desc.attributes[1].format = MTLVertexFormatFloat2;
    vtx_desc.attributes[1].offset = offsetof(UIVertex, uv);
    vtx_desc.attributes[1].bufferIndex = 0;
    vtx_desc.attributes[2].format = MTLVertexFormatFloat4;
    vtx_desc.attributes[2].offset = offsetof(UIVertex, col);
    vtx_desc.attributes[2].bufferIndex = 0;
    vtx_desc.attributes[3].format = MTLVertexFormatFloat2;
    vtx_desc.attributes[3].offset = offsetof(UIVertex, rect_min);
    vtx_desc.attributes[3].bufferIndex = 0;
    vtx_desc.attributes[4].format = MTLVertexFormatFloat2;
    vtx_desc.attributes[4].offset = offsetof(UIVertex, rect_max);
    vtx_desc.attributes[4].bufferIndex = 0;
    vtx_desc.attributes[5].format = MTLVertexFormatFloat4;
    vtx_desc.attributes[5].offset = offsetof(UIVertex, corner_radii);
    vtx_desc.attributes[5].bufferIndex = 0;
    vtx_desc.attributes[6].format = MTLVertexFormatFloat;
    vtx_desc.attributes[6].offset = offsetof(UIVertex, border_thickness);
    vtx_desc.attributes[6].bufferIndex = 0;
    vtx_desc.attributes[7].format = MTLVertexFormatFloat;
    vtx_desc.attributes[7].offset = offsetof(UIVertex, softness);
    vtx_desc.attributes[7].bufferIndex = 0;
    vtx_desc.attributes[8].format = MTLVertexFormatFloat;
    vtx_desc.attributes[8].offset = offsetof(UIVertex, mode);
    vtx_desc.attributes[8].bufferIndex = 0;
    vtx_desc.layouts[0].stride = sizeof(UIVertex);
    vtx_desc.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;

    // Pipeline state
    MTLRenderPipelineDescriptor *pipe_desc = [[MTLRenderPipelineDescriptor alloc] init];
    pipe_desc.vertexFunction = vert_fn;
    pipe_desc.fragmentFunction = frag_fn;
    pipe_desc.vertexDescriptor = vtx_desc;
    pipe_desc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
    pipe_desc.colorAttachments[0].blendingEnabled = YES;
    pipe_desc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
    pipe_desc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
    pipe_desc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
    pipe_desc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
    pipe_desc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
    pipe_desc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;

    r->pipeline_state = [r->device newRenderPipelineStateWithDescriptor:pipe_desc error:&error];
    if (!r->pipeline_state) {
        NSLog(@"Metal pipeline error: %@", error);
        free(r);
        return NULL;
    }

    // Create F64-buffered vertex/index buffers
    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        r->vertex_buffers[i] = [r->device newBufferWithLength:MAX_VERTICES * sizeof(UIVertex)
                                                      options:MTLResourceStorageModeShared];
        r->index_buffers[i] = [r->device newBufferWithLength:MAX_INDICES * sizeof(U32)
                                                     options:MTLResourceStorageModeShared];
    }

    // FreeType + font atlas
    init_freetype(r);
    if (!create_font_atlas(r, 22.0f)) {
        free(r);
        return NULL;
    }

    // Default clear color (dark theme bg_dark)
    r->clear_r = 0.12f;
    r->clear_g = 0.12f;
    r->clear_b = 0.13f;

    return r;
}

Renderer *renderer_create_shared(Renderer *parent, RendererDesc *desc) {
    if (!parent) return NULL;

    Renderer *r = (Renderer *)calloc(1, sizeof(Renderer));

    r->parent      = parent;
    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;

    // Share from parent
    r->device         = parent->device;
    r->command_queue  = parent->command_queue;
    r->pipeline_state = parent->pipeline_state;
    r->font_texture   = parent->font_texture;
    r->font_sampler   = parent->font_sampler;
    r->icon_texture   = parent->icon_texture;
    r->ft_lib         = parent->ft_lib;
    r->ft_face        = parent->ft_face;
    memcpy(r->glyphs, parent->glyphs, sizeof(r->glyphs));
    r->font_atlas_size  = parent->font_atlas_size;
    r->font_line_height = parent->font_line_height;

    // Create own metal layer for the popup's NSView
    NSView *view = (__bridge NSView *)desc->window_handle;
    [view setWantsLayer:YES];

    CAMetalLayer *layer = [CAMetalLayer layer];
    layer.device = r->device;
    layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
    layer.framebufferOnly = YES;
    layer.displaySyncEnabled = YES;
    layer.maximumDrawableCount = 3;

    NSWindow *window = [view window];
    r->backing_scale = (F32)[window backingScaleFactor];
    layer.contentsScale = r->backing_scale;
    layer.drawableSize = CGSizeMake(r->width, r->height);

    [view setLayer:layer];
    r->metal_layer = layer;

    r->frame_semaphore = dispatch_semaphore_create(NUM_BACK_BUFFERS);

    // Create own vertex/index buffers
    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        r->vertex_buffers[i] = [r->device newBufferWithLength:MAX_VERTICES * sizeof(UIVertex)
                                                      options:MTLResourceStorageModeShared];
        r->index_buffers[i] = [r->device newBufferWithLength:MAX_INDICES * sizeof(U32)
                                                     options:MTLResourceStorageModeShared];
    }

    // Default clear color
    r->clear_r = parent->clear_r;
    r->clear_g = parent->clear_g;
    r->clear_b = parent->clear_b;

    return r;
}

void renderer_destroy(Renderer *r) {
    if (!r) return;
    // Only free FreeType resources if we own them (not shared)
    if (!r->parent) {
        if (r->ft_face) FT_Done_Face(r->ft_face);
        if (r->ft_lib)  FT_Done_FreeType(r->ft_lib);
    }
    free(r);
}

B32 renderer_begin_frame(Renderer *r) {
    if (r->width <= 0 || r->height <= 0) return false;

    // Sync shared resources from parent (font atlas may have been rebuilt)
    if (r->parent) {
        r->font_texture   = r->parent->font_texture;
        r->font_sampler   = r->parent->font_sampler;
        r->icon_texture   = r->parent->icon_texture;
        memcpy(r->glyphs, r->parent->glyphs, sizeof(r->glyphs));
        r->font_atlas_size  = r->parent->font_atlas_size;
        r->font_line_height = r->parent->font_line_height;
    }

    // Wait for an in-flight frame to finish, then acquire a drawable.
    // Doing this BEFORE input sampling ensures the freshest mouse position
    // is used for rendering, reducing perceived drag latency.
    dispatch_semaphore_wait(r->frame_semaphore, DISPATCH_TIME_FOREVER);
    @autoreleasepool {
        r->current_drawable = [[r->metal_layer nextDrawable] retain];
    }
    if (!r->current_drawable) {
        dispatch_semaphore_signal(r->frame_semaphore);
        return false;
    }
    return true;
}

// Helper context for batch flushing (replaces C++ lambdas)
typedef struct FlushCtx {
    DrawBatch *batch;
    U32 *flush_index_start;
    S32 *bound_texture;
    Renderer *r;
    U32 buf_idx;
    id<MTLRenderCommandEncoder> encoder;
} FlushCtx;

static void flush_batch(FlushCtx *ctx) {
    U32 index_count = ctx->batch->index_count - *ctx->flush_index_start;
    if (index_count == 0) return;

    [ctx->encoder setVertexBuffer:ctx->r->vertex_buffers[ctx->buf_idx] offset:0 atIndex:0];
    [ctx->encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
                             indexCount:index_count
                              indexType:MTLIndexTypeUInt32
                            indexBuffer:ctx->r->index_buffers[ctx->buf_idx]
                      indexBufferOffset:*ctx->flush_index_start * sizeof(U32)];

    *ctx->flush_index_start = ctx->batch->index_count;
}

static void bind_font_texture(FlushCtx *ctx) {
    if (*ctx->bound_texture != 0) {
        flush_batch(ctx);
        [ctx->encoder setFragmentTexture:ctx->r->font_texture atIndex:0];
        *ctx->bound_texture = 0;
    }
}

static void bind_icon_texture(FlushCtx *ctx) {
    if (*ctx->bound_texture != 1 && ctx->r->icon_texture) {
        flush_batch(ctx);
        [ctx->encoder setFragmentTexture:ctx->r->icon_texture atIndex:0];
        *ctx->bound_texture = 1;
    }
}

void renderer_end_frame(Renderer *r, Clay_RenderCommandArray *render_commands) {
    @autoreleasepool {
        id<CAMetalDrawable> drawable = r->current_drawable;
        r->current_drawable = nil;

        U32 buf_idx = r->frame_index % NUM_BACK_BUFFERS;

        MTLRenderPassDescriptor *pass = [MTLRenderPassDescriptor renderPassDescriptor];
        pass.colorAttachments[0].texture = drawable.texture;
        pass.colorAttachments[0].loadAction = MTLLoadActionClear;
        pass.colorAttachments[0].storeAction = MTLStoreActionStore;
        pass.colorAttachments[0].clearColor = MTLClearColorMake(r->clear_r, r->clear_g, r->clear_b, 1.0);

        id<MTLCommandBuffer> cmd_buf = [r->command_queue commandBuffer];
        id<MTLRenderCommandEncoder> encoder = [cmd_buf renderCommandEncoderWithDescriptor:pass];

        [encoder setRenderPipelineState:r->pipeline_state];
        [encoder setFragmentTexture:r->font_texture atIndex:0];
        [encoder setFragmentSamplerState:r->font_sampler atIndex:0];

        // Viewport
        MTLViewport viewport = {0};
        viewport.width = (F64)r->width;
        viewport.height = (F64)r->height;
        viewport.zfar = 1.0;
        [encoder setViewport:viewport];

        // Full scissor
        MTLScissorRect full_scissor = { 0, 0, (NSUInteger)r->width, (NSUInteger)r->height };
        [encoder setScissorRect:full_scissor];

        // Constants
        F32 constants[2] = { (F32)r->width, (F32)r->height };
        [encoder setVertexBytes:constants length:sizeof(constants) atIndex:1];

        // Process Clay render commands
        if (render_commands->length > 0) {
            DrawBatch batch = {0};
            batch.vertices = (UIVertex *)[r->vertex_buffers[buf_idx] contents];
            batch.indices = (U32 *)[r->index_buffers[buf_idx] contents];
            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;

            FlushCtx fctx;
            fctx.batch = &batch;
            fctx.flush_index_start = &flush_index_start;
            fctx.bound_texture = &bound_texture;
            fctx.r = r;
            fctx.buf_idx = buf_idx;
            fctx.encoder = encoder;

            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_texture(&fctx);
                    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_batch(&fctx);
                    NSUInteger sx = (NSUInteger)Max(bb.x, 0.f);
                    NSUInteger sy = (NSUInteger)Max(bb.y, 0.f);
                    NSUInteger sw = (NSUInteger)Min(bb.width, (F32)r->width - (F32)sx);
                    NSUInteger sh = (NSUInteger)Min(bb.height, (F32)r->height - (F32)sy);
                    if (sw == 0) sw = 1;
                    if (sh == 0) sh = 1;
                    MTLScissorRect clip = { sx, sy, sw, sh };
                    [encoder setScissorRect:clip];
                } break;

                case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
                    flush_batch(&fctx);
                    [encoder setScissorRect: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_texture(&fctx);
                            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_texture(&fctx);
                            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_texture(&fctx);
                            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_batch(&fctx);
        }

        [encoder endEncoding];
        [cmd_buf presentDrawable:drawable];

        __block dispatch_semaphore_t sem = r->frame_semaphore;
        [cmd_buf addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull) {
            dispatch_semaphore_signal(sem);
        }];

        [cmd_buf commit];
        [drawable release];
    }

    r->frame_index++;
}

void renderer_create_icon_atlas(Renderer *r, const U8 *data, S32 w, S32 h) {
    MTLTextureDescriptor *tex_desc = [[MTLTextureDescriptor alloc] init];
    tex_desc.pixelFormat = MTLPixelFormatRGBA8Unorm;
    tex_desc.width = w;
    tex_desc.height = h;
    tex_desc.usage = MTLTextureUsageShaderRead;

    r->icon_texture = [r->device newTextureWithDescriptor:tex_desc];
    [r->icon_texture replaceRegion:MTLRegionMake2D(0, 0, w, h)
                       mipmapLevel:0
                         withBytes:data
                       bytesPerRow:w * 4];
}

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;
    [r->font_texture release];
    [r->font_sampler release];
    r->font_texture = nil;
    r->font_sampler = nil;
    create_font_atlas(r, target_size);
}

void renderer_sync_from_parent(Renderer *r) {
    if (!r || !r->parent) return;
    Renderer *p = r->parent;
    r->font_texture    = p->font_texture;
    r->font_sampler    = p->font_sampler;
    r->font_atlas_size = p->font_atlas_size;
    r->font_line_height = p->font_line_height;
    memcpy(r->glyphs, p->glyphs, sizeof(r->glyphs));
}

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

    NSWindow *window = [(__bridge NSView *)r->metal_layer.delegate window];
    if (window) {
        r->backing_scale = (F32)[window backingScaleFactor];
        r->metal_layer.contentsScale = r->backing_scale;
    }
    r->metal_layer.drawableSize = CGSizeMake(width, height);
}
