#include "game/editor.h"
#include "game/entity_registry.h"
#include "engine/core.h"
#include "engine/input.h"
#include "engine/renderer.h"
#include "engine/assets.h"
#include "engine/camera.h"
#include "engine/font.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#ifdef __EMSCRIPTEN__
#include <emscripten.h>

/* ═══════════════════════════════════════════════════
 *  Browser file I/O helpers (Emscripten only)
 *
 *  Save:  write .lvl to virtual FS, then trigger
 *         browser download via Blob URL.
 *  Load:  open an <input type="file"> dialog, read
 *         the file contents, write to virtual FS,
 *         and set a flag for the editor to pick up.
 * ═══════════════════════════════════════════════════ */

/* Download a file from the Emscripten virtual FS to the user's machine */
EM_JS(void, browser_download_file, (const char *vfs_path, const char *download_name), {
    var path = UTF8ToString(vfs_path);
    var name = UTF8ToString(download_name);
    try {
        var data = FS.readFile(path);
        var blob = new Blob([data], { type: 'text/plain' });
        var url  = URL.createObjectURL(blob);
        var a    = document.createElement('a');
        a.href = url;
        a.download = name;
        document.body.appendChild(a);
        a.click();
        setTimeout(function() {
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }, 100);
    } catch (e) {
        console.error('Download failed:', e);
    }
});

/* Path where uploaded file will be written in virtual FS */
#define UPLOAD_VFS_PATH "/tmp/_upload.lvl"

/* Flags polled by editor_update — set by JS */
static volatile int s_upload_ready  = 0; /* file upload completed       */
static volatile int s_save_request  = 0; /* Ctrl+S or Save button       */
static volatile int s_load_request  = 0; /* Ctrl+O or Load button       */

/* Path to load when s_load_request is 2 (load a specific VFS file) */
static char s_load_vfs_path[256] = {0};

/* Open a browser file picker; on selection, write contents to virtual FS */
EM_JS(void, browser_open_file_picker, (), {
    var input = document.createElement('input');
    input.type = 'file';
    input.accept = '.lvl,.txt';
    input.onchange = function(e) {
        var file = e.target.files[0];
        if (!file) return;
        var reader = new FileReader();
        reader.onload = function(ev) {
            var data = new Uint8Array(ev.target.result);
            try { FS.unlink('/tmp/_upload.lvl'); } catch(x) {}
            FS.writeFile('/tmp/_upload.lvl', data);
            /* Set the C flag via direct heap access */
            var ptr = _editor_upload_flag_ptr();
            HEAP32[ptr >> 2] = 1;
        };
        reader.readAsArrayBuffer(file);
    };
    input.click();
});

/* Export addresses so JS can set flags and paths */
EMSCRIPTEN_KEEPALIVE
int *editor_upload_flag_ptr(void) {
    return (int *)&s_upload_ready;
}

EMSCRIPTEN_KEEPALIVE
int *editor_save_flag_ptr(void) {
    return (int *)&s_save_request;
}

EMSCRIPTEN_KEEPALIVE
int *editor_load_flag_ptr(void) {
    return (int *)&s_load_request;
}

/* Called from JS to load an existing level file from the virtual FS */
EMSCRIPTEN_KEEPALIVE
void editor_load_vfs_file(const char *path) {
    snprintf(s_load_vfs_path, sizeof(s_load_vfs_path), "%s", path);
    s_load_request = 2; /* 2 = load from specific VFS path */
}

#endif /* __EMSCRIPTEN__ */

/* Bitmap font provided by engine/font.h (included above). */

/* ═══════════════════════════════════════════════════
 *  8x8 pixel mini-icons for the entity palette
 *
 *  Each icon is 8 pixels wide, 8 pixels tall.
 *  Stored as 8 rows of 8 bits packed in a uint64_t.
 *  Row 0 is the top row, stored in bits 56-63.
 *  Bit 7 of each byte = leftmost pixel.
 * ═══════════════════════════════════════════════════ */

#define ICON_SIZE 8

static const uint64_t s_icon_bitmaps[ICON_COUNT] = {
    /* ICON_GRUNT: spiky creature */
    0x5A3C7EDB7E3C5A00ULL,
    /* ICON_FLYER: bat with wings */
    0x0018187E7E241800ULL,
    /* ICON_TURRET: mounted cannon */
    0x0010107C7C381000ULL,
    /* ICON_PLATFORM: horizontal bar */
    0x000000007E7E0000ULL,
    /* ICON_PLATFORM_V: vertical bar */
    0x0018181818180000ULL,
    /* ICON_FLAME: fire */
    0x0010385454381000ULL,
    /* ICON_FORCEFIELD: electric bars */
    0x2424FF2424FF2424ULL,
    /* ICON_HEART: classic heart shape */
    0x006C7E7E3E1C0800ULL,
    /* ICON_BOLT: lightning bolt */
    0x0C1C18387060C000ULL,
    /* ICON_FUEL: fuel canister */
    0x003C4242423C1800ULL,
    /* ICON_DRONE: quad-rotor */
    0x00427E3C3C7E4200ULL,
    /* ICON_GUN: pistol shape */
    0x00007C7C10100000ULL,
    /* ICON_ASTEROID: jagged rock */
    0x001C3E7F7F3E1C00ULL,
    /* ICON_SPACECRAFT: ship */
    0x0018183C7E7E2400ULL,
    /* ICON_LASER: laser turret (box with beam line) */
    0x003C3C18187E0000ULL,
    /* ICON_LASER_TRACK: tracking laser (box with rotating beam) */
    0x003C3C1818660000ULL,
    /* ICON_CHARGER: arrow/charging creature */
    0x0018187E7E181800ULL,
    /* ICON_SPAWNER: pulsing core with dots */
    0x24003C3C3C002400ULL,
};

static void draw_icon(SDL_Renderer *r, EditorIcon icon,
                      int x, int y, SDL_Color col) {
    if (icon < 0 || icon >= ICON_COUNT) return;
    uint64_t bits = s_icon_bitmaps[icon];
    SDL_SetRenderDrawColor(r, col.r, col.g, col.b, col.a);

    for (int row = 0; row < ICON_SIZE; row++) {
        int byte = (int)((bits >> (56 - row * 8)) & 0xFF);
        for (int col_bit = 0; col_bit < ICON_SIZE; col_bit++) {
            if (byte & (1 << (7 - col_bit))) {
                SDL_RenderDrawPoint(r, x + col_bit, y + row);
            }
        }
    }
}

/* ═══════════════════════════════════════════════════
 *  Constants
 * ═══════════════════════════════════════════════════ */

#define CAM_PAN_SPEED    200.0f
#define ZOOM_MIN         0.25f
#define ZOOM_MAX         2.0f
#define ZOOM_STEP        0.25f

/* ═══════════════════════════════════════════════════
 *  Available tilesets (cycle with T key)
 * ═══════════════════════════════════════════════════ */

static const char *s_tilesets[] = {
    "assets/tiles/tileset.png",
    "assets/tiles/moon_tileset.png",
};
#define TILESET_COUNT ((int)(sizeof(s_tilesets) / sizeof(s_tilesets[0])))

static const SDL_Color COL_BG       = {30, 30, 46, 255};
static const SDL_Color COL_PANEL    = {20, 20, 35, 255};
static const SDL_Color COL_PANEL_LT = {40, 40, 60, 255};
static const SDL_Color COL_TEXT     = {200, 200, 220, 255};
static const SDL_Color COL_TEXT_DIM = {120, 120, 140, 255};
static const SDL_Color COL_HIGHLIGHT= {255, 200, 60, 255};
static const SDL_Color COL_GRID     = {60, 60, 80, 80};
static const SDL_Color COL_SPAWN    = {60, 255, 60, 255};
static const SDL_Color COL_ENTITY   = {255, 100, 100, 255};
static const SDL_Color COL_SELECT   = {255, 255, 100, 255};

static const SDL_Color COL_EXIT     = {50, 230, 180, 255};

/* Tool names */
static const char *s_tool_names[TOOL_COUNT] = {
    "PENCIL", "ERASER", "FILL", "ENTITY", "SPAWN", "EXIT"
};

/* Layer names */
static const char *s_layer_names[EDITOR_LAYER_COUNT] = {
    "COL", "BG", "FG"
};

/* ═══════════════════════════════════════════════════
 *  Layer access helpers
 * ═══════════════════════════════════════════════════ */

static uint16_t *get_layer(Tilemap *map, EditorLayer layer) {
    switch (layer) {
        case EDITOR_LAYER_COLLISION: return map->collision_layer;
        case EDITOR_LAYER_BG:       return map->bg_layer;
        case EDITOR_LAYER_FG:       return map->fg_layer;
        default:                    return map->collision_layer;
    }
}

static uint16_t get_tile(const Tilemap *map, const uint16_t *layer, int tx, int ty) {
    if (tx < 0 || tx >= map->width || ty < 0 || ty >= map->height) return 0;
    return layer[ty * map->width + tx];
}

static void set_tile(Tilemap *map, uint16_t *layer, int tx, int ty, uint16_t id) {
    if (tx < 0 || tx >= map->width || ty < 0 || ty >= map->height) return;
    layer[ty * map->width + tx] = id;
}

/* ═══════════════════════════════════════════════════
 *  Flood fill (scanline algorithm)
 *
 *  Processes entire horizontal spans at once, then
 *  pushes only the boundary seeds above and below.
 *  Much more memory-efficient than naive 4-neighbor
 *  fill on large maps (O(height) stack vs O(area)).
 * ═══════════════════════════════════════════════════ */

typedef struct FillSpan { int x_left, x_right, y, dir; } FillSpan;

static void flood_fill(Tilemap *map, uint16_t *layer, int sx, int sy, uint16_t new_id) {
    uint16_t old_id = get_tile(map, layer, sx, sy);
    if (old_id == new_id) return;

    int capacity = 1024;
    FillSpan *stack = malloc(capacity * sizeof(FillSpan));
    if (!stack) return;
    int top = 0;

    /* Fill the initial span containing (sx, sy). */
    int xl = sx, xr = sx;
    while (xl > 0 && get_tile(map, layer, xl - 1, sy) == old_id) xl--;
    while (xr < map->width - 1 && get_tile(map, layer, xr + 1, sy) == old_id) xr++;
    for (int x = xl; x <= xr; x++) set_tile(map, layer, x, sy, new_id);

    /* Seed rows above and below. */
    stack[top++] = (FillSpan){xl, xr, sy, -1};
    stack[top++] = (FillSpan){xl, xr, sy,  1};

    while (top > 0) {
        FillSpan s = stack[--top];
        int ny = s.y + s.dir;
        if (ny < 0 || ny >= map->height) continue;

        /* Scan the row for sub-spans that match old_id and are seeded
         * by the parent span [x_left, x_right]. */
        int x = s.x_left;
        while (x <= s.x_right) {
            /* Skip non-matching tiles. */
            if (get_tile(map, layer, x, ny) != old_id) { x++; continue; }

            /* Found a matching run; expand it fully. */
            int run_l = x;
            while (run_l > 0 && get_tile(map, layer, run_l - 1, ny) == old_id) run_l--;
            int run_r = x;
            while (run_r < map->width - 1 && get_tile(map, layer, run_r + 1, ny) == old_id) run_r++;

            /* Fill this run. */
            for (int fx = run_l; fx <= run_r; fx++) set_tile(map, layer, fx, ny, new_id);

            /* Grow stack if needed. */
            if (top + 2 >= capacity) {
                capacity *= 2;
                FillSpan *tmp = realloc(stack, capacity * sizeof(FillSpan));
                if (!tmp) { fprintf(stderr, "Warning: flood fill out of memory\n"); free(stack); return; }
                stack = tmp;
            }

            /* Continue in the same direction. */
            stack[top++] = (FillSpan){run_l, run_r, ny, s.dir};
            /* Also seed the opposite direction for portions that extend
             * beyond the parent span (leak-around fills). */
            if (run_l < s.x_left)
                stack[top++] = (FillSpan){run_l, s.x_left - 1, ny, -s.dir};
            if (run_r > s.x_right) {
                if (top + 1 >= capacity) {
                    capacity *= 2;
                    FillSpan *tmp = realloc(stack, capacity * sizeof(FillSpan));
                    if (!tmp) { fprintf(stderr, "Warning: flood fill out of memory\n"); free(stack); return; }
                    stack = tmp;
                }
                stack[top++] = (FillSpan){s.x_right + 1, run_r, ny, -s.dir};
            }

            x = run_r + 1;
        }
    }

    free(stack);
}

/* ═══════════════════════════════════════════════════
 *  Tilemap save (reuses .lvl format)
 * ═══════════════════════════════════════════════════ */

static bool save_tilemap(const Tilemap *map, const char *path) {
    FILE *f = fopen(path, "w");
    if (!f) {
        fprintf(stderr, "editor: failed to write %s\n", path);
        return false;
    }

    fprintf(f, "# Level created with in-game editor\n\n");
    /* Write the tileset path stored in the map, falling back to default */
    fprintf(f, "TILESET %s\n",
            map->tileset_path[0] ? map->tileset_path : "assets/tiles/tileset.png");
    fprintf(f, "SIZE %d %d\n", map->width, map->height);

    /* Player spawn in tile coords */
    int stx = (int)(map->player_spawn.x / TILE_SIZE);
    int sty = (int)(map->player_spawn.y / TILE_SIZE);
    fprintf(f, "SPAWN %d %d\n", stx, sty);

    if (map->gravity > 0)
        fprintf(f, "GRAVITY %.0f\n", map->gravity);
    if (map->wind != 0.0f)
        fprintf(f, "WIND %.0f\n", map->wind);
    if (map->has_bg_color)
        fprintf(f, "BG_COLOR %d %d %d\n", map->bg_color.r, map->bg_color.g, map->bg_color.b);
    if (map->music_path[0])
        fprintf(f, "MUSIC %s\n", map->music_path);
    if (map->parallax_far_path[0])
        fprintf(f, "PARALLAX_FAR %s\n", map->parallax_far_path);
    if (map->parallax_near_path[0])
        fprintf(f, "PARALLAX_NEAR %s\n", map->parallax_near_path);
    if (map->parallax_style > 0)
        fprintf(f, "PARALLAX_STYLE %d\n", map->parallax_style);
    if (map->player_unarmed)
        fprintf(f, "PLAYER_UNARMED\n");

    fprintf(f, "\n");

    /* Entity spawns */
    for (int i = 0; i < map->entity_spawn_count; i++) {
        const EntitySpawn *es = &map->entity_spawns[i];
        int tx = (int)(es->x / TILE_SIZE);
        int ty = (int)(es->y / TILE_SIZE);
        fprintf(f, "ENTITY %s %d %d\n", es->type_name, tx, ty);
    }
    fprintf(f, "\n");

    /* Exit zones */
    for (int i = 0; i < map->exit_zone_count; i++) {
        const ExitZone *ez = &map->exit_zones[i];
        int tx = (int)(ez->x / TILE_SIZE);
        int ty = (int)(ez->y / TILE_SIZE);
        int tw = (int)(ez->w / TILE_SIZE);
        int th = (int)(ez->h / TILE_SIZE);
        if (ez->target[0]) {
            fprintf(f, "EXIT %d %d %d %d %s\n", tx, ty, tw, th, ez->target);
        } else {
            fprintf(f, "EXIT %d %d %d %d\n", tx, ty, tw, th);
        }
    }
    if (map->exit_zone_count > 0) fprintf(f, "\n");

    /* Tile definitions — save all entries so flags and tex coords survive
     * round-trips, including tiles at position (0,0) with no flags. */
    for (int id = 1; id < map->tile_def_count; id++) {
        const TileDef *td = &map->tile_defs[id];
        fprintf(f, "TILEDEF %d %d %d %u\n", id, td->tex_x, td->tex_y, td->flags);
    }
    fprintf(f, "\n");

    /* Write each layer */
    const char *layer_names[] = {"collision", "bg", "fg"};
    const uint16_t *layers[] = {map->collision_layer, map->bg_layer, map->fg_layer};

    for (int l = 0; l < 3; l++) {
        /* Skip empty layers */
        bool has_data = false;
        for (int i = 0; i < map->width * map->height; i++) {
            if (layers[l][i]) { has_data = true; break; }
        }
        if (!has_data && l != 0) continue; /* always write collision */

        fprintf(f, "LAYER %s\n", layer_names[l]);
        for (int y = 0; y < map->height; y++) {
            for (int x = 0; x < map->width; x++) {
                if (x > 0) fprintf(f, " ");
                fprintf(f, "%d", layers[l][y * map->width + x]);
            }
            fprintf(f, "\n");
        }
        fprintf(f, "\n");
    }

    fclose(f);
    printf("editor: saved to %s\n", path);
    return true;
}

/* ═══════════════════════════════════════════════════
 *  Editor resize
 * ═══════════════════════════════════════════════════ */

static void resize_layer(uint16_t **layer, int old_w, int old_h, int new_w, int new_h) {
    uint16_t *new_data = calloc(new_w * new_h, sizeof(uint16_t));
    int copy_w = old_w < new_w ? old_w : new_w;
    int copy_h = old_h < new_h ? old_h : new_h;
    for (int y = 0; y < copy_h; y++) {
        for (int x = 0; x < copy_w; x++) {
            new_data[y * new_w + x] = (*layer)[y * old_w + x];
        }
    }
    free(*layer);
    *layer = new_data;
}

static void editor_resize(Editor *ed, int new_w, int new_h) {
    if (new_w < 10) new_w = 10;
    if (new_h < 10) new_h = 10;
    if (new_w > MAX_MAP_SIZE) new_w = MAX_MAP_SIZE;
    if (new_h > MAX_MAP_SIZE) new_h = MAX_MAP_SIZE;

    int old_w = ed->map.width;
    int old_h = ed->map.height;

    resize_layer(&ed->map.collision_layer, old_w, old_h, new_w, new_h);
    resize_layer(&ed->map.bg_layer,        old_w, old_h, new_w, new_h);
    resize_layer(&ed->map.fg_layer,        old_w, old_h, new_w, new_h);

    ed->map.width = new_w;
    ed->map.height = new_h;
    ed->dirty = true;

    camera_set_bounds(&ed->camera,
        (float)(new_w * TILE_SIZE),
        (float)(new_h * TILE_SIZE));
}

/* ═══════════════════════════════════════════════════
 *  Discover tileset dimensions
 * ═══════════════════════════════════════════════════ */

static void discover_tileset(Editor *ed) {
    if (!ed->map.tileset) {
        ed->tileset_cols = 0;
        ed->tileset_rows = 0;
        ed->tileset_total = 0;
        return;
    }
    int tex_w, tex_h;
    SDL_QueryTexture(ed->map.tileset, NULL, NULL, &tex_w, &tex_h);
    ed->tileset_cols = tex_w / TILE_SIZE;
    ed->tileset_rows = tex_h / TILE_SIZE;
    ed->tileset_total = ed->tileset_cols * ed->tileset_rows;
}

/* ═══════════════════════════════════════════════════
 *  Tileset switching
 * ═══════════════════════════════════════════════════ */

/* Find current tileset index in the table, or -1 */
static int find_tileset_index(const char *path) {
    for (int i = 0; i < TILESET_COUNT; i++) {
        if (strcmp(path, s_tilesets[i]) == 0) return i;
    }
    return -1;
}

/* Switch the editor to a different tileset by path */
static void editor_switch_tileset(Editor *ed, const char *path) {
    SDL_Texture *tex = assets_get_texture(path);
    if (!tex) {
        fprintf(stderr, "editor: cannot load tileset %s\n", path);
        return;
    }
    snprintf(ed->map.tileset_path, sizeof(ed->map.tileset_path), "%s", path);
    ed->map.tileset = tex;
    int tex_w;
    SDL_QueryTexture(tex, NULL, NULL, &tex_w, NULL);
    ed->map.tileset_cols = tex_w / TILE_SIZE;
    discover_tileset(ed);
    ed->tile_palette_scroll = 0;
    ed->dirty = true;
}

/* Cycle to the next tileset in the table.
 * If the current tileset is not in the list, cycle to the first entry. */
static void editor_cycle_tileset(Editor *ed) {
    int idx = find_tileset_index(ed->map.tileset_path);
    int next = (idx < 0) ? 0 : (idx + 1) % TILESET_COUNT;
    editor_switch_tileset(ed, s_tilesets[next]);
    printf("editor: tileset -> %s\n", s_tilesets[next]);
}

/* ── Tile flag helpers ───────────────────────────── */

/* Ensure a tile ID has a TileDef entry. Creates one if missing. */
static void ensure_tile_def(Tilemap *map, uint16_t tile_id) {
    if (tile_id == 0 || tile_id >= MAX_TILE_DEFS) return;
    if (tile_id < map->tile_def_count) return;
    /* Fill gaps with default entries (tex pos from grid, no flags) */
    int cols = map->tileset_cols > 0 ? map->tileset_cols : 16;
    for (int id = map->tile_def_count; id <= tile_id; id++) {
        map->tile_defs[id].tex_x = (uint16_t)((id - 1) % cols);
        map->tile_defs[id].tex_y = (uint16_t)((id - 1) / cols);
        map->tile_defs[id].flags = 0;
    }
    map->tile_def_count = tile_id + 1;
}

/* Cycle the flag on the currently selected tile: none -> solid -> platform -> hazard -> none */
static void editor_cycle_tile_flag(Editor *ed) {
    uint16_t id = ed->selected_tile;
    if (id == 0 || id >= MAX_TILE_DEFS) return;
    ensure_tile_def(&ed->map, id);
    uint32_t cur = ed->map.tile_defs[id].flags;
    /* Cycle: 0 -> SOLID -> PLATFORM -> HAZARD -> 0 */
    if (cur & TILE_HAZARD)       ed->map.tile_defs[id].flags = 0;
    else if (cur & TILE_PLATFORM) ed->map.tile_defs[id].flags = TILE_HAZARD;
    else if (cur & TILE_SOLID)    ed->map.tile_defs[id].flags = TILE_PLATFORM;
    else                          ed->map.tile_defs[id].flags = TILE_SOLID;
    ed->dirty = true;
}

/* Get display name for the flags of the selected tile */
static const char *tile_flag_name(uint32_t flags) {
    if (flags & TILE_HAZARD)   return "HAZARD";
    if (flags & TILE_PLATFORM) return "PLATFORM";
    if (flags & TILE_SOLID)    return "SOLID";
    return "NONE";
}

/* ═══════════════════════════════════════════════════
 *  Lifecycle
 * ═══════════════════════════════════════════════════ */

void editor_init(Editor *ed) {
    memset(ed, 0, sizeof(Editor));
    ed->tool = TOOL_PENCIL;
    ed->active_layer = EDITOR_LAYER_COLLISION;
    ed->selected_tile = 1;
    ed->selected_entity = 0;
    ed->show_grid = true;
    ed->show_all_layers = true;
    ed->dragging_entity = -1;
    ed->active = true;
    camera_init(&ed->camera, SCREEN_WIDTH, SCREEN_HEIGHT);

    /* Ensure entity registry is populated for the palette.
     * If we came from gameplay it is already initialized;
     * if the editor was launched directly it will be empty. */
    if (g_entity_registry.count == 0) {
        entity_registry_populate();
    }
}

void editor_new_level(Editor *ed, int width, int height) {
    /* Free any existing map */
    tilemap_free(&ed->map);

    memset(&ed->map, 0, sizeof(Tilemap));
    ed->map.width = width;
    ed->map.height = height;

    int total = width * height;
    ed->map.collision_layer = calloc(total, sizeof(uint16_t));
    ed->map.bg_layer        = calloc(total, sizeof(uint16_t));
    ed->map.fg_layer        = calloc(total, sizeof(uint16_t));

    /* Default tile defs (same as levelgen) */
    ed->map.tile_defs[1] = (TileDef){0, 0, TILE_SOLID};
    ed->map.tile_defs[2] = (TileDef){1, 0, TILE_SOLID};
    ed->map.tile_defs[3] = (TileDef){2, 0, TILE_SOLID};
    ed->map.tile_defs[4] = (TileDef){0, 1, TILE_PLATFORM};
    ed->map.tile_def_count = 5;

    /* Default spawn */
    ed->map.player_spawn = vec2(3.0f * TILE_SIZE, (height - 4) * TILE_SIZE);
    ed->map.gravity = DEFAULT_GRAVITY;

    /* Load tileset */
    snprintf(ed->map.tileset_path, sizeof(ed->map.tileset_path),
             "%s", "assets/tiles/tileset.png");
    ed->map.tileset = assets_get_texture(ed->map.tileset_path);
    if (ed->map.tileset) {
        int tex_w;
        SDL_QueryTexture(ed->map.tileset, NULL, NULL, &tex_w, NULL);
        ed->map.tileset_cols = tex_w / TILE_SIZE;
    }
    discover_tileset(ed);

    camera_init(&ed->camera, SCREEN_WIDTH, SCREEN_HEIGHT);
    camera_set_bounds(&ed->camera,
        (float)(width * TILE_SIZE),
        (float)(height * TILE_SIZE));

    ed->has_file = false;
    ed->dirty = false;
    ed->file_path[0] = '\0';

    printf("editor: new level %dx%d\n", width, height);
}

bool editor_load(Editor *ed, const char *path) {
    tilemap_free(&ed->map);
    memset(&ed->map, 0, sizeof(Tilemap));

    if (!tilemap_load(&ed->map, path, g_engine.renderer)) {
        return false;
    }

    discover_tileset(ed);

    camera_init(&ed->camera, SCREEN_WIDTH, SCREEN_HEIGHT);
    camera_set_bounds(&ed->camera,
        (float)(ed->map.width * TILE_SIZE),
        (float)(ed->map.height * TILE_SIZE));

    strncpy(ed->file_path, path, sizeof(ed->file_path) - 1);
    ed->has_file = true;
    ed->dirty = false;

    printf("editor: loaded %s\n", path);
    return true;
}

bool editor_save(Editor *ed) {
    if (!ed->has_file) {
        /* Default path */
        strncpy(ed->file_path, "assets/levels/edited.lvl",
                sizeof(ed->file_path) - 1);
        ed->has_file = true;
    }
    if (save_tilemap(&ed->map, ed->file_path)) {
        ed->dirty = false;
#ifdef __EMSCRIPTEN__
        /* Trigger a browser download of the saved file */
        {
            /* Extract just the filename from the path for the download name */
            const char *name = strrchr(ed->file_path, '/');
            name = name ? name + 1 : ed->file_path;
            browser_download_file(ed->file_path, name);
        }
#endif
        return true;
    }
    return false;
}

bool editor_save_as(Editor *ed, const char *path) {
    strncpy(ed->file_path, path, sizeof(ed->file_path) - 1);
    ed->has_file = true;
    return editor_save(ed);
}

void editor_free(Editor *ed) {
    tilemap_free(&ed->map);
    ed->active = false;
}

/* ═══════════════════════════════════════════════════
 *  Coordinate helpers
 * ═══════════════════════════════════════════════════ */

/* Is the screen position inside the canvas area (not on UI panels)? */
static bool in_canvas(int sx, int sy) {
    return sx < (SCREEN_WIDTH - EDITOR_PALETTE_W) &&
           sy >= EDITOR_TOOLBAR_H &&
           sy < (SCREEN_HEIGHT - EDITOR_STATUS_H);
}

/* Screen position to world tile coordinates */
static void screen_to_tile(const Editor *ed, int sx, int sy, int *tx, int *ty) {
    Vec2 world = camera_screen_to_world(&ed->camera, vec2((float)sx, (float)sy));
    *tx = world_to_tile(world.x);
    *ty = world_to_tile(world.y);
}

/* ═══════════════════════════════════════════════════
 *  Entity helpers
 * ═══════════════════════════════════════════════════ */

/* Find entity spawn at a tile position, return index or -1 */
static int find_entity_at(const Tilemap *map, float wx, float wy) {
    for (int i = 0; i < map->entity_spawn_count; i++) {
        const EntitySpawn *es = &map->entity_spawns[i];
        const EntityRegEntry *reg = entity_registry_find(es->type_name);
        float ew = reg ? (float)reg->width : TILE_SIZE;
        float eh = reg ? (float)reg->height : TILE_SIZE;
        if (wx >= es->x && wx < es->x + ew &&
            wy >= es->y && wy < es->y + eh) {
            return i;
        }
    }
    return -1;
}

static void add_entity_spawn(Tilemap *map, const char *name, float wx, float wy) {
    if (map->entity_spawn_count >= MAX_ENTITY_SPAWNS) return;
    EntitySpawn *es = &map->entity_spawns[map->entity_spawn_count];
    strncpy(es->type_name, name, 31);
    es->type_name[31] = '\0';
    /* Snap to tile grid */
    es->x = floorf(wx / TILE_SIZE) * TILE_SIZE;
    es->y = floorf(wy / TILE_SIZE) * TILE_SIZE;
    map->entity_spawn_count++;
}

static void remove_entity_spawn(Tilemap *map, int index) {
    if (index < 0 || index >= map->entity_spawn_count) return;
    /* Shift remaining entries */
    for (int i = index; i < map->entity_spawn_count - 1; i++) {
        map->entity_spawns[i] = map->entity_spawns[i + 1];
    }
    map->entity_spawn_count--;
}

/* ── Exit zone helpers ─────────────────────────────── */

#define EXIT_DEFAULT_W  2   /* default exit zone width in tiles  */
#define EXIT_DEFAULT_H  3   /* default exit zone height in tiles */

static int find_exit_at(const Tilemap *map, float wx, float wy) {
    for (int i = 0; i < map->exit_zone_count; i++) {
        const ExitZone *ez = &map->exit_zones[i];
        if (wx >= ez->x && wx < ez->x + ez->w &&
            wy >= ez->y && wy < ez->y + ez->h) {
            return i;
        }
    }
    return -1;
}

static void add_exit_zone(Tilemap *map, float wx, float wy) {
    if (map->exit_zone_count >= MAX_EXIT_ZONES) return;
    ExitZone *ez = &map->exit_zones[map->exit_zone_count];
    /* Snap to tile grid */
    ez->x = floorf(wx / TILE_SIZE) * TILE_SIZE;
    ez->y = floorf(wy / TILE_SIZE) * TILE_SIZE;
    ez->w = EXIT_DEFAULT_W * TILE_SIZE;
    ez->h = EXIT_DEFAULT_H * TILE_SIZE;
    ez->target[0] = '\0'; /* empty = victory / end of level */
    map->exit_zone_count++;
}

static void remove_exit_zone(Tilemap *map, int index) {
    if (index < 0 || index >= map->exit_zone_count) return;
    for (int i = index; i < map->exit_zone_count - 1; i++) {
        map->exit_zones[i] = map->exit_zones[i + 1];
    }
    map->exit_zone_count--;
}

/* ═══════════════════════════════════════════════════
 *  Internal state for test-play / quit requests
 * ═══════════════════════════════════════════════════ */

static bool s_wants_test_play = false;
static bool s_wants_quit = false;

bool editor_wants_test_play(Editor *ed) {
    (void)ed;
    bool v = s_wants_test_play;
    s_wants_test_play = false;
    return v;
}

bool editor_wants_quit(Editor *ed) {
    (void)ed;
    bool v = s_wants_quit;
    s_wants_quit = false;
    return v;
}

/* ═══════════════════════════════════════════════════
 *  Update — input handling
 * ═══════════════════════════════════════════════════ */

void editor_update(Editor *ed, float dt) {
    int mx, my;
    input_mouse_pos(&mx, &my);

    /* ── Keyboard shortcuts ────────────────── */

    /* Tool selection: 1-5 */
    if (input_key_pressed(SDL_SCANCODE_1)) ed->tool = TOOL_PENCIL;
    if (input_key_pressed(SDL_SCANCODE_2)) ed->tool = TOOL_ERASER;
    if (input_key_pressed(SDL_SCANCODE_3)) ed->tool = TOOL_FILL;
    if (input_key_pressed(SDL_SCANCODE_4)) ed->tool = TOOL_ENTITY;
    if (input_key_pressed(SDL_SCANCODE_5)) ed->tool = TOOL_SPAWN;
    if (input_key_pressed(SDL_SCANCODE_6)) ed->tool = TOOL_EXIT;

    /* Layer selection: Q/W/E */
    if (input_key_pressed(SDL_SCANCODE_Q)) ed->active_layer = EDITOR_LAYER_COLLISION;
    if (input_key_pressed(SDL_SCANCODE_W)) ed->active_layer = EDITOR_LAYER_BG;
    if (input_key_pressed(SDL_SCANCODE_E) && !input_key_held(SDL_SCANCODE_LCTRL))
        ed->active_layer = EDITOR_LAYER_FG;

    /* Grid toggle: G */
    if (input_key_pressed(SDL_SCANCODE_G)) ed->show_grid = !ed->show_grid;

    /* Layer visibility toggle: V */
    if (input_key_pressed(SDL_SCANCODE_V)) ed->show_all_layers = !ed->show_all_layers;

    /* Tileset cycle: T */
    if (input_key_pressed(SDL_SCANCODE_T) && !input_key_held(SDL_SCANCODE_LCTRL))
        editor_cycle_tileset(ed);

    /* Tile flag cycle: F (solid/platform/hazard/none) */
    if (input_key_pressed(SDL_SCANCODE_F) && !input_key_held(SDL_SCANCODE_LCTRL))
        editor_cycle_tile_flag(ed);

    /* Save: Ctrl+S */
    if (input_key_pressed(SDL_SCANCODE_S) && input_key_held(SDL_SCANCODE_LCTRL)) {
        editor_save(ed);
    }

    /* Open / Load: Ctrl+O */
    if (input_key_pressed(SDL_SCANCODE_O) && input_key_held(SDL_SCANCODE_LCTRL)) {
#ifdef __EMSCRIPTEN__
        browser_open_file_picker();
#else
        /* Desktop: reload from current file (no native file dialog) */
        if (ed->has_file) {
            editor_load(ed, ed->file_path);
        }
#endif
    }

#ifdef __EMSCRIPTEN__
    /* Poll for save request from JS (Ctrl+S or Save button) */
    if (s_save_request) {
        s_save_request = 0;
        editor_save(ed);
    }

    /* Poll for load request from JS */
    if (s_load_request == 1) {
        /* 1 = open file picker (Ctrl+O or Load button) */
        s_load_request = 0;
        browser_open_file_picker();
    } else if (s_load_request == 2) {
        /* 2 = load a specific VFS file (level picker dropdown) */
        s_load_request = 0;
        if (s_load_vfs_path[0]) {
            if (editor_load(ed, s_load_vfs_path)) {
                printf("editor: loaded %s\n", s_load_vfs_path);
            }
            s_load_vfs_path[0] = '\0';
        }
    }

    /* Poll for completed file upload from browser */
    if (s_upload_ready) {
        s_upload_ready = 0;
        if (editor_load(ed, UPLOAD_VFS_PATH)) {
            /* Clear the stored path so saves go to default,
             * not the tmp upload path */
            strncpy(ed->file_path, "assets/levels/edited.lvl",
                    sizeof(ed->file_path) - 1);
            ed->has_file = true;
            printf("editor: loaded from browser upload\n");
        }
    }
#endif

    /* Test play: P */
    if (input_key_pressed(SDL_SCANCODE_P)) {
        s_wants_test_play = true;
    }

    /* Quit editor: Escape */
    if (input_key_pressed(SDL_SCANCODE_ESCAPE)) {
        s_wants_quit = true;
    }

    /* Resize: Ctrl+Shift+Plus/Minus for height, Ctrl+Plus/Minus for width */
    if (input_key_held(SDL_SCANCODE_LCTRL)) {
        if (input_key_held(SDL_SCANCODE_LSHIFT)) {
            /* Height resize takes priority when Shift is also held */
            if (input_key_pressed(SDL_SCANCODE_EQUALS))
                editor_resize(ed, ed->map.width, ed->map.height + 5);
            if (input_key_pressed(SDL_SCANCODE_MINUS))
                editor_resize(ed, ed->map.width, ed->map.height - 5);
        } else {
            /* Width resize only when Shift is NOT held */
            if (input_key_pressed(SDL_SCANCODE_EQUALS))
                editor_resize(ed, ed->map.width + 10, ed->map.height);
            if (input_key_pressed(SDL_SCANCODE_MINUS))
                editor_resize(ed, ed->map.width - 10, ed->map.height);
        }
    }

    /* ── Camera panning (arrow keys or WASD with no ctrl) ── */
    if (!input_key_held(SDL_SCANCODE_LCTRL)) {
        float pan_speed = CAM_PAN_SPEED * dt;
        /* Faster when zoomed out */
        if (ed->camera.zoom > 0.0f)
            pan_speed /= ed->camera.zoom;

        if (input_key_held(SDL_SCANCODE_LEFT)  || input_key_held(SDL_SCANCODE_A))
            ed->camera.pos.x -= pan_speed;
        if (input_key_held(SDL_SCANCODE_RIGHT) || input_key_held(SDL_SCANCODE_D))
            ed->camera.pos.x += pan_speed;
        if (input_key_held(SDL_SCANCODE_UP))
            ed->camera.pos.y -= pan_speed;
        if (input_key_held(SDL_SCANCODE_DOWN)  || input_key_held(SDL_SCANCODE_S))
            ed->camera.pos.y += pan_speed;
    }

    /* Middle mouse drag panning */
    {
        static int last_mx = 0, last_my = 0;
        static bool dragging_cam = false;
        if (input_mouse_held(MOUSE_MIDDLE)) {
            if (!dragging_cam) {
                dragging_cam = true;
                last_mx = mx;
                last_my = my;
            } else {
                float inv_zoom = (ed->camera.zoom > 0.0f) ? (1.0f / ed->camera.zoom) : 1.0f;
                ed->camera.pos.x -= (float)(mx - last_mx) * inv_zoom;
                ed->camera.pos.y -= (float)(my - last_my) * inv_zoom;
                last_mx = mx;
                last_my = my;
            }
        } else {
            dragging_cam = false;
        }
    }

    /* ── Zoom (scroll wheel) — only when mouse is over the canvas ── */
    int scroll = input_mouse_scroll();
    if (scroll != 0 && in_canvas(mx, my)) {
        /* Compute world pos under mouse at CURRENT zoom, before changing it */
        Vec2 mouse_world = camera_screen_to_world(&ed->camera, vec2((float)mx, (float)my));

        float old_zoom = ed->camera.zoom;
        ed->camera.zoom += (float)scroll * ZOOM_STEP;
        if (ed->camera.zoom < ZOOM_MIN) ed->camera.zoom = ZOOM_MIN;
        if (ed->camera.zoom > ZOOM_MAX) ed->camera.zoom = ZOOM_MAX;

        /* Zoom toward mouse position: keep the world point under the
         * mouse cursor in the same screen position after the zoom. */
        if (ed->camera.zoom != old_zoom) {
            float new_zoom = ed->camera.zoom;
            ed->camera.pos.x = mouse_world.x - (float)mx / new_zoom;
            ed->camera.pos.y = mouse_world.y - (float)my / new_zoom;
        }
    }

    /* Clamp camera */
    float vp_w = ed->camera.viewport.x;
    float vp_h = ed->camera.viewport.y;
    if (ed->camera.zoom > 0.0f) {
        vp_w /= ed->camera.zoom;
        vp_h /= ed->camera.zoom;
    }
    float max_x = ed->camera.bounds_max.x - vp_w;
    float max_y = ed->camera.bounds_max.y - vp_h;
    if (ed->camera.pos.x < ed->camera.bounds_min.x - vp_w * 0.5f)
        ed->camera.pos.x = ed->camera.bounds_min.x - vp_w * 0.5f;
    if (ed->camera.pos.y < ed->camera.bounds_min.y - vp_h * 0.5f)
        ed->camera.pos.y = ed->camera.bounds_min.y - vp_h * 0.5f;
    if (max_x > 0 && ed->camera.pos.x > max_x + vp_w * 0.5f)
        ed->camera.pos.x = max_x + vp_w * 0.5f;
    if (max_y > 0 && ed->camera.pos.y > max_y + vp_h * 0.5f)
        ed->camera.pos.y = max_y + vp_h * 0.5f;

    /* ── Right palette click ── */
    if (mx >= SCREEN_WIDTH - EDITOR_PALETTE_W && my >= EDITOR_TOOLBAR_H &&
        my < SCREEN_HEIGHT - EDITOR_STATUS_H) {
        int px = SCREEN_WIDTH - EDITOR_PALETTE_W;
        int py = EDITOR_TOOLBAR_H;
        int ph = SCREEN_HEIGHT - EDITOR_TOOLBAR_H - EDITOR_STATUS_H;

        /* Split point: tiles take ~55%, entities take ~45% */
        int tile_section_h = (ph * 55) / 100;
        int ent_section_y = py + tile_section_h;

        if (my < ent_section_y) {
            /* Tile palette area */
            if (input_mouse_pressed(MOUSE_LEFT)) {
                int pal_x = mx - px - 2;
                int pal_label_h = FONT_H + 6;
                int pal_y = my - py - pal_label_h
                            + ed->tile_palette_scroll * (TILE_SIZE + 1);
                if (pal_x >= 0 && pal_y >= 0) {
                    int max_cols = (EDITOR_PALETTE_W - 4) / (TILE_SIZE + 1);
                    if (max_cols < 1) max_cols = 1;
                    int tile_col = pal_x / (TILE_SIZE + 1);
                    int tile_row = pal_y / (TILE_SIZE + 1);
                    int tile_id = tile_row * max_cols + tile_col + 1;
                    if (tile_id >= 1 && tile_id <= ed->tileset_total) {
                        ed->selected_tile = (uint16_t)tile_id;
                        /* Auto-switch to pencil tool when selecting a tile */
                        if (ed->tool == TOOL_ENTITY) ed->tool = TOOL_PENCIL;
                    }
                }
            }
            if (scroll != 0) {
                ed->tile_palette_scroll -= scroll;
                if (ed->tile_palette_scroll < 0) ed->tile_palette_scroll = 0;
            }
        } else {
            /* Entity palette area */
            if (input_mouse_pressed(MOUSE_LEFT)) {
                int pal_label_h = FONT_H + 6;
                int pal_y = my - ent_section_y - pal_label_h
                            + ed->entity_palette_scroll * 12;
                int entry_idx = pal_y / 12;
                if (entry_idx >= 0 && entry_idx < g_entity_registry.count) {
                    ed->selected_entity = entry_idx;
                    /* Auto-switch to entity tool when selecting an entity */
                    ed->tool = TOOL_ENTITY;
                }
            }
            if (scroll != 0) {
                ed->entity_palette_scroll -= scroll;
                if (ed->entity_palette_scroll < 0) ed->entity_palette_scroll = 0;
            }
        }
        return; /* Don't process canvas clicks when on palette */
    }

    /* ── Toolbar click ── */
    if (my < EDITOR_TOOLBAR_H) {
        if (input_mouse_pressed(MOUSE_LEFT)) {
            /* Tool buttons: each 35px wide starting at x=2 */
            int btn = (mx - 2) / 35;
            if (btn >= 0 && btn < TOOL_COUNT) {
                ed->tool = (EditorTool)btn;
            }
            /* Layer buttons after separator */
            int sep_x = TOOL_COUNT * 35 + 8;
            int lx = mx - sep_x;
            if (lx >= 0) {
                int lbtn = lx / 25;
                if (lbtn >= 0 && lbtn < EDITOR_LAYER_COUNT) {
                    ed->active_layer = (EditorLayer)lbtn;
                }
            }
        }
        return;
    }

    /* ── Canvas interaction ── */
    if (!in_canvas(mx, my)) return;

    int tile_x, tile_y;
    screen_to_tile(ed, mx, my, &tile_x, &tile_y);

    Vec2 world_pos = camera_screen_to_world(&ed->camera, vec2((float)mx, (float)my));

    switch (ed->tool) {
    case TOOL_PENCIL:
        if (input_mouse_held(MOUSE_LEFT)) {
            uint16_t *layer = get_layer(&ed->map, ed->active_layer);
            /* Ensure tile has an explicit TileDef so it does not fall
             * through to the "unknown tile = solid" default. */
            ensure_tile_def(&ed->map, ed->selected_tile);
            set_tile(&ed->map, layer, tile_x, tile_y, ed->selected_tile);
            ed->dirty = true;
        }
        if (input_mouse_held(MOUSE_RIGHT)) {
            /* Pick tile under cursor */
            uint16_t *layer = get_layer(&ed->map, ed->active_layer);
            uint16_t t = get_tile(&ed->map, layer, tile_x, tile_y);
            if (t != 0) ed->selected_tile = t;
        }
        break;

    case TOOL_ERASER:
        if (input_mouse_held(MOUSE_LEFT)) {
            uint16_t *layer = get_layer(&ed->map, ed->active_layer);
            set_tile(&ed->map, layer, tile_x, tile_y, 0);
            ed->dirty = true;
        }
        break;

    case TOOL_FILL:
        if (input_mouse_pressed(MOUSE_LEFT)) {
            uint16_t *layer = get_layer(&ed->map, ed->active_layer);
            ensure_tile_def(&ed->map, ed->selected_tile);
            flood_fill(&ed->map, layer, tile_x, tile_y, ed->selected_tile);
            ed->dirty = true;
        }
        if (input_mouse_pressed(MOUSE_RIGHT)) {
            uint16_t *layer = get_layer(&ed->map, ed->active_layer);
            flood_fill(&ed->map, layer, tile_x, tile_y, 0);
            ed->dirty = true;
        }
        break;

    case TOOL_ENTITY:
        if (input_mouse_pressed(MOUSE_LEFT)) {
            /* Check if clicking an existing entity */
            int eidx = find_entity_at(&ed->map, world_pos.x, world_pos.y);
            if (eidx >= 0) {
                /* Start dragging */
                ed->dragging_entity = eidx;
                ed->drag_offset_x = ed->map.entity_spawns[eidx].x - world_pos.x;
                ed->drag_offset_y = ed->map.entity_spawns[eidx].y - world_pos.y;
            } else {
                /* Place new entity */
                if (ed->selected_entity >= 0 && ed->selected_entity < g_entity_registry.count) {
                    add_entity_spawn(&ed->map,
                        g_entity_registry.entries[ed->selected_entity].name,
                        world_pos.x, world_pos.y);
                    ed->dirty = true;
                }
            }
        }
        if (input_mouse_held(MOUSE_LEFT) && ed->dragging_entity >= 0) {
            /* Drag entity — snap to grid */
            float nx = floorf((world_pos.x + ed->drag_offset_x) / TILE_SIZE) * TILE_SIZE;
            float ny = floorf((world_pos.y + ed->drag_offset_y) / TILE_SIZE) * TILE_SIZE;
            ed->map.entity_spawns[ed->dragging_entity].x = nx;
            ed->map.entity_spawns[ed->dragging_entity].y = ny;
            ed->dirty = true;
        }
        if (input_mouse_released(MOUSE_LEFT)) {
            ed->dragging_entity = -1;
        }
        /* Right-click to delete entity */
        if (input_mouse_pressed(MOUSE_RIGHT)) {
            int eidx = find_entity_at(&ed->map, world_pos.x, world_pos.y);
            if (eidx >= 0) {
                remove_entity_spawn(&ed->map, eidx);
                ed->dirty = true;
            }
        }
        break;

    case TOOL_SPAWN:
        if (input_mouse_pressed(MOUSE_LEFT)) {
            ed->map.player_spawn = vec2(
                floorf(world_pos.x / TILE_SIZE) * TILE_SIZE,
                floorf(world_pos.y / TILE_SIZE) * TILE_SIZE
            );
            ed->dirty = true;
        }
        break;

    case TOOL_EXIT:
        if (input_mouse_pressed(MOUSE_LEFT)) {
            /* Place a new exit zone (if not clicking an existing one) */
            int eidx = find_exit_at(&ed->map, world_pos.x, world_pos.y);
            if (eidx < 0) {
                add_exit_zone(&ed->map, world_pos.x, world_pos.y);
                ed->dirty = true;
            }
        }
        /* Right-click to delete exit zone */
        if (input_mouse_pressed(MOUSE_RIGHT)) {
            int eidx = find_exit_at(&ed->map, world_pos.x, world_pos.y);
            if (eidx >= 0) {
                remove_exit_zone(&ed->map, eidx);
                ed->dirty = true;
            }
        }
        break;

    default:
        break;
    }
}

/* ═══════════════════════════════════════════════════
 *  Render
 * ═══════════════════════════════════════════════════ */

void editor_render(Editor *ed, float interpolation) {
    (void)interpolation;

    SDL_Renderer *r = g_engine.renderer;
    Camera *cam = &ed->camera;

    /* ── Clear background ── */
    SDL_SetRenderDrawColor(r, COL_BG.r, COL_BG.g, COL_BG.b, 255);
    SDL_RenderClear(r);

    /* ── Render tile layers ── */
    if (ed->show_all_layers) {
        /* Draw all layers, dim inactive ones */
        for (int l = 0; l < EDITOR_LAYER_COUNT; l++) {
            uint16_t *layer = get_layer(&ed->map, (EditorLayer)l);
            if (l != (int)ed->active_layer) {
                SDL_SetTextureAlphaMod(ed->map.tileset, 80);
            } else {
                SDL_SetTextureAlphaMod(ed->map.tileset, 255);
            }
            tilemap_render_layer(&ed->map, layer, cam, r);
        }
        SDL_SetTextureAlphaMod(ed->map.tileset, 255);
    } else {
        /* Only active layer */
        uint16_t *layer = get_layer(&ed->map, ed->active_layer);
        tilemap_render_layer(&ed->map, layer, cam, r);
    }

    /* ── Grid ── */
    if (ed->show_grid) {
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
        SDL_SetRenderDrawColor(r, COL_GRID.r, COL_GRID.g, COL_GRID.b, COL_GRID.a);

        float inv_zoom = (cam->zoom > 0.0f) ? (1.0f / cam->zoom) : 1.0f;
        int start_tx = (int)floorf(cam->pos.x / TILE_SIZE);
        int start_ty = (int)floorf(cam->pos.y / TILE_SIZE);
        int end_tx = start_tx + (int)(cam->viewport.x * inv_zoom / TILE_SIZE) + 2;
        int end_ty = start_ty + (int)(cam->viewport.y * inv_zoom / TILE_SIZE) + 2;

        if (start_tx < 0) start_tx = 0;
        if (start_ty < 0) start_ty = 0;
        if (end_tx > ed->map.width) end_tx = ed->map.width;
        if (end_ty > ed->map.height) end_ty = ed->map.height;

        /* Vertical lines */
        for (int x = start_tx; x <= end_tx; x++) {
            Vec2 top = camera_world_to_screen(cam, vec2(tile_to_world(x), tile_to_world(start_ty)));
            Vec2 bot = camera_world_to_screen(cam, vec2(tile_to_world(x), tile_to_world(end_ty)));
            SDL_RenderDrawLine(r, (int)top.x, (int)top.y, (int)bot.x, (int)bot.y);
        }
        /* Horizontal lines */
        for (int y = start_ty; y <= end_ty; y++) {
            Vec2 left  = camera_world_to_screen(cam, vec2(tile_to_world(start_tx), tile_to_world(y)));
            Vec2 right_pt = camera_world_to_screen(cam, vec2(tile_to_world(end_tx), tile_to_world(y)));
            SDL_RenderDrawLine(r, (int)left.x, (int)left.y, (int)right_pt.x, (int)right_pt.y);
        }
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
    }

    /* ── Hazard tile overlay (collision layer) ── */
    if (ed->map.collision_layer) {
        float inv_zoom = (cam->zoom > 0.0f) ? (1.0f / cam->zoom) : 1.0f;
        int hs_tx = (int)floorf(cam->pos.x / TILE_SIZE) - 1;
        int hs_ty = (int)floorf(cam->pos.y / TILE_SIZE) - 1;
        int he_tx = hs_tx + (int)(cam->viewport.x * inv_zoom / TILE_SIZE) + 3;
        int he_ty = hs_ty + (int)(cam->viewport.y * inv_zoom / TILE_SIZE) + 3;
        if (hs_tx < 0) hs_tx = 0;
        if (hs_ty < 0) hs_ty = 0;
        if (he_tx > ed->map.width) he_tx = ed->map.width;
        if (he_ty > ed->map.height) he_ty = ed->map.height;

        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
        for (int y = hs_ty; y < he_ty; y++) {
            for (int x = hs_tx; x < he_tx; x++) {
                uint16_t tid = ed->map.collision_layer[y * ed->map.width + x];
                if (tid == 0 || tid >= ed->map.tile_def_count) continue;
                if (!(ed->map.tile_defs[tid].flags & TILE_HAZARD)) continue;
                Vec2 sp = camera_world_to_screen(cam,
                    vec2(tile_to_world(x), tile_to_world(y)));
                float zs = TILE_SIZE * cam->zoom;
                SDL_SetRenderDrawColor(r, 255, 60, 30, 60);
                SDL_Rect hr = {(int)sp.x, (int)sp.y,
                               (int)(zs + 0.5f), (int)(zs + 0.5f)};
                SDL_RenderFillRect(r, &hr);
            }
        }
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
    }

    /* ── Level boundary ── */
    {
        SDL_SetRenderDrawColor(r, 200, 200, 255, 120);
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
        Vec2 tl = camera_world_to_screen(cam, vec2(0, 0));
        Vec2 br = camera_world_to_screen(cam,
            vec2((float)(ed->map.width * TILE_SIZE), (float)(ed->map.height * TILE_SIZE)));
        SDL_Rect border = {(int)tl.x, (int)tl.y, (int)(br.x - tl.x), (int)(br.y - tl.y)};
        SDL_RenderDrawRect(r, &border);
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
    }

    /* ── Entity spawn markers ── */
    for (int i = 0; i < ed->map.entity_spawn_count; i++) {
        const EntitySpawn *es = &ed->map.entity_spawns[i];
        const EntityRegEntry *reg = entity_registry_find(es->type_name);
        SDL_Color col = reg ? reg->color : COL_ENTITY;
        float ew = reg ? (float)reg->width : TILE_SIZE;
        float eh = reg ? (float)reg->height : TILE_SIZE;

        Vec2 sp = camera_world_to_screen(cam, vec2(es->x, es->y));
        float zw = ew * cam->zoom;
        float zh = eh * cam->zoom;

        SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 180);
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
        SDL_Rect er = {(int)sp.x, (int)sp.y, (int)(zw + 0.5f), (int)(zh + 0.5f)};
        SDL_RenderFillRect(r, &er);
        SDL_SetRenderDrawColor(r, 255, 255, 255, 200);
        SDL_RenderDrawRect(r, &er);
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);

        /* Draw icon or first letter of entity name */
        if (reg && zw >= ICON_SIZE + 2 && reg->icon >= 0 && reg->icon < ICON_COUNT) {
            draw_icon(r, (EditorIcon)reg->icon,
                      (int)sp.x + 1, (int)sp.y + 1, COL_TEXT);
        } else if (reg && reg->display[0] && zw >= 6) {
            font_draw_char(r, reg->display[0], (int)sp.x + 1, (int)sp.y + 1, COL_TEXT);
        }
    }

    /* ── Player spawn marker ── */
    {
        Vec2 sp = camera_world_to_screen(cam, ed->map.player_spawn);
        float zs = TILE_SIZE * cam->zoom;
        SDL_SetRenderDrawColor(r, COL_SPAWN.r, COL_SPAWN.g, COL_SPAWN.b, 200);
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
        SDL_Rect sr = {(int)sp.x, (int)sp.y, (int)(zs + 0.5f), (int)(zs + 0.5f)};
        SDL_RenderDrawRect(r, &sr);
        font_draw_text(r, "SP", (int)sp.x + 1, (int)sp.y + 1, COL_SPAWN);
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
    }

    /* ── Exit zone markers ── */
    for (int i = 0; i < ed->map.exit_zone_count; i++) {
        const ExitZone *ez = &ed->map.exit_zones[i];
        Vec2 sp = camera_world_to_screen(cam, vec2(ez->x, ez->y));
        float zw = ez->w * cam->zoom;
        float zh = ez->h * cam->zoom;

        SDL_SetRenderDrawColor(r, COL_EXIT.r, COL_EXIT.g, COL_EXIT.b, 80);
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
        SDL_Rect er = {(int)sp.x, (int)sp.y, (int)(zw + 0.5f), (int)(zh + 0.5f)};
        SDL_RenderFillRect(r, &er);
        SDL_SetRenderDrawColor(r, COL_EXIT.r, COL_EXIT.g, COL_EXIT.b, 220);
        SDL_RenderDrawRect(r, &er);
        font_draw_text(r, "EXIT", (int)sp.x + 1, (int)sp.y + 1, COL_EXIT);
        if (ez->target[0]) {
            font_draw_text(r, ez->target, (int)sp.x + 1, (int)sp.y + 8, COL_EXIT);
        }
        SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
    }

    /* ── Cursor highlight ── */
    {
        int mx_c, my_c;
        input_mouse_pos(&mx_c, &my_c);
        if (in_canvas(mx_c, my_c)) {
            int tx, ty;
            screen_to_tile(ed, mx_c, my_c, &tx, &ty);
            if (tx >= 0 && tx < ed->map.width && ty >= 0 && ty < ed->map.height) {
                Vec2 cpos = camera_world_to_screen(cam,
                    vec2(tile_to_world(tx), tile_to_world(ty)));
                float zs = TILE_SIZE * cam->zoom;

                /* Exit tool: show 2x3 preview */
                int cw = 1, ch = 1;
                if (ed->tool == TOOL_EXIT) {
                    cw = EXIT_DEFAULT_W;
                    ch = EXIT_DEFAULT_H;
                }

                SDL_SetRenderDrawColor(r,
                    ed->tool == TOOL_EXIT ? COL_EXIT.r : COL_SELECT.r,
                    ed->tool == TOOL_EXIT ? COL_EXIT.g : COL_SELECT.g,
                    ed->tool == TOOL_EXIT ? COL_EXIT.b : COL_SELECT.b, 120);
                SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
                SDL_Rect cr = {(int)cpos.x, (int)cpos.y,
                               (int)(zs * cw + 0.5f), (int)(zs * ch + 0.5f)};
                SDL_RenderDrawRect(r, &cr);
                SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
            }
        }
    }

    /* ═════════════════════════════════════════
     *  UI Panels (drawn in screen space)
     * ═════════════════════════════════════════ */

    /* ── Top toolbar ── */
    {
        SDL_SetRenderDrawColor(r, COL_PANEL.r, COL_PANEL.g, COL_PANEL.b, COL_PANEL.a);
        SDL_Rect tb = {0, 0, SCREEN_WIDTH, EDITOR_TOOLBAR_H};
        SDL_RenderFillRect(r, &tb);

        /* Vertically center text: (EDITOR_TOOLBAR_H - FONT_H) / 2 */
        int text_y = (EDITOR_TOOLBAR_H - FONT_H) / 2;

        /* Tool buttons */
        for (int i = 0; i < TOOL_COUNT; i++) {
            int bx = i * 35 + 2;
            SDL_Color tc = (i == (int)ed->tool) ? COL_HIGHLIGHT : COL_TEXT_DIM;
            font_draw_text(r, s_tool_names[i], bx, text_y, tc);
        }

        /* Separator */
        int sep_x = TOOL_COUNT * 35 + 4;
        SDL_SetRenderDrawColor(r, COL_PANEL_LT.r, COL_PANEL_LT.g, COL_PANEL_LT.b, 255);
        SDL_RenderDrawLine(r, sep_x, 3, sep_x, EDITOR_TOOLBAR_H - 3);

        /* Layer buttons */
        int layer_start = sep_x + 4;
        for (int i = 0; i < EDITOR_LAYER_COUNT; i++) {
            int bx = layer_start + i * 25;
            SDL_Color lc = (i == (int)ed->active_layer) ? COL_HIGHLIGHT : COL_TEXT_DIM;
            font_draw_text(r, s_layer_names[i], bx, text_y, lc);
        }

        /* Grid & Layers indicators */
        int grid_x = layer_start + EDITOR_LAYER_COUNT * 25 + 4;
        font_draw_text(r, ed->show_grid ? "[G]RID" : "[G]rid", grid_x, text_y,
                  ed->show_grid ? COL_TEXT : COL_TEXT_DIM);

        /* Tileset switch hint */
        int ts_x = grid_x + 7 * (FONT_W + 1) + 4;
        font_draw_text(r, "[T]SET", ts_x, text_y, COL_TEXT_DIM);
    }

    /* ── Right palette panel ── */
    {
        int px = SCREEN_WIDTH - EDITOR_PALETTE_W;
        int py = EDITOR_TOOLBAR_H;
        int ph = SCREEN_HEIGHT - EDITOR_TOOLBAR_H - EDITOR_STATUS_H;

        SDL_SetRenderDrawColor(r, COL_PANEL.r, COL_PANEL.g, COL_PANEL.b, COL_PANEL.a);
        SDL_Rect panel = {px, py, EDITOR_PALETTE_W, ph};
        SDL_RenderFillRect(r, &panel);

        /* Left separator line */
        SDL_SetRenderDrawColor(r, COL_PANEL_LT.r, COL_PANEL_LT.g, COL_PANEL_LT.b, 255);
        SDL_RenderDrawLine(r, px, py, px, py + ph);

        /* Split: tiles top ~55%, entities bottom ~45% */
        int tile_section_h = (ph * 55) / 100;
        int ent_section_y = py + tile_section_h;

        /* ── Tile palette (top section) ── */
        {
            int label_h = FONT_H + 6; /* label area: font + padding */
            /* Show tileset filename (strip directory) */
            {
                const char *ts_name = strrchr(ed->map.tileset_path, '/');
                ts_name = ts_name ? ts_name + 1 : ed->map.tileset_path;
                font_draw_text(r, ts_name[0] ? ts_name : "TILES",
                          px + 2, py + (label_h - FONT_H) / 2, COL_TEXT);
            }

            int pal_y_start = py + label_h;
            int max_cols = (EDITOR_PALETTE_W - 4) / (TILE_SIZE + 1);
            if (max_cols < 1) max_cols = 1;

            if (ed->map.tileset && ed->tileset_total > 0) {
                SDL_Rect clip = {px, pal_y_start, EDITOR_PALETTE_W,
                                 tile_section_h - label_h};
                SDL_RenderSetClipRect(r, &clip);

                for (int id = 1; id <= ed->tileset_total; id++) {
                    int col_idx = (id - 1) % max_cols;
                    int row_idx = (id - 1) / max_cols;

                    int draw_x = px + 2 + col_idx * (TILE_SIZE + 1);
                    int draw_y = pal_y_start + row_idx * (TILE_SIZE + 1)
                                 - ed->tile_palette_scroll * (TILE_SIZE + 1);

                    if (draw_y + TILE_SIZE < pal_y_start ||
                        draw_y > ent_section_y) continue;

                    /* Use TileDef tex coords when available, else grid position */
                    int src_col, src_row;
                    if (id < ed->map.tile_def_count) {
                        src_col = ed->map.tile_defs[id].tex_x;
                        src_row = ed->map.tile_defs[id].tex_y;
                    } else {
                        src_col = (id - 1) % ed->tileset_cols;
                        src_row = (id - 1) / ed->tileset_cols;
                    }
                    SDL_Rect src = {
                        src_col * TILE_SIZE, src_row * TILE_SIZE,
                        TILE_SIZE, TILE_SIZE
                    };
                    SDL_Rect dst = {draw_x, draw_y, TILE_SIZE, TILE_SIZE};
                    SDL_RenderCopy(r, ed->map.tileset, &src, &dst);

                    /* Flag indicator: colored dot for solid/platform/hazard */
                    if (id < ed->map.tile_def_count) {
                        uint32_t fl = ed->map.tile_defs[id].flags;
                        if (fl & TILE_HAZARD) {
                            SDL_SetRenderDrawColor(r, 255, 80, 40, 255);
                            SDL_Rect dot = {draw_x + TILE_SIZE - 3, draw_y, 3, 3};
                            SDL_RenderFillRect(r, &dot);
                        } else if (fl & TILE_PLATFORM) {
                            SDL_SetRenderDrawColor(r, 80, 200, 255, 255);
                            SDL_Rect dot = {draw_x + TILE_SIZE - 3, draw_y, 3, 3};
                            SDL_RenderFillRect(r, &dot);
                        } else if (fl & TILE_SOLID) {
                            SDL_SetRenderDrawColor(r, 200, 200, 200, 180);
                            SDL_Rect dot = {draw_x + TILE_SIZE - 3, draw_y, 3, 3};
                            SDL_RenderFillRect(r, &dot);
                        }
                    }

                    /* Highlight selected */
                    if (id == (int)ed->selected_tile) {
                        SDL_SetRenderDrawColor(r, COL_HIGHLIGHT.r, COL_HIGHLIGHT.g,
                                               COL_HIGHLIGHT.b, 255);
                        SDL_Rect sel = {draw_x - 1, draw_y - 1,
                                        TILE_SIZE + 2, TILE_SIZE + 2};
                        SDL_RenderDrawRect(r, &sel);
                    }
                }

                SDL_RenderSetClipRect(r, NULL);
            }

            /* Show selected tile's flag below palette */
            if (ed->selected_tile > 0 && ed->selected_tile < MAX_TILE_DEFS) {
                uint32_t flags = 0;
                if (ed->selected_tile < ed->map.tile_def_count)
                    flags = ed->map.tile_defs[ed->selected_tile].flags;
                const char *fname = tile_flag_name(flags);
                SDL_Color fc = (flags & TILE_HAZARD) ? (SDL_Color){255, 80, 40, 255} :
                               (flags & TILE_PLATFORM) ? (SDL_Color){80, 200, 255, 255} :
                               (flags & TILE_SOLID) ? COL_TEXT : COL_TEXT_DIM;
                font_draw_text(r, fname, px + 2, ent_section_y - FONT_H - 2, fc);
                /* Show [F] hint */
                int fw = (int)strlen(fname) * (FONT_W + 1);
                font_draw_text(r, "[F]", px + 2 + fw + 2, ent_section_y - FONT_H - 2, COL_TEXT_DIM);
            }
        }

        /* ── Divider line between tiles and entities ── */
        SDL_SetRenderDrawColor(r, COL_PANEL_LT.r, COL_PANEL_LT.g, COL_PANEL_LT.b, 255);
        SDL_RenderDrawLine(r, px + 2, ent_section_y, px + EDITOR_PALETTE_W - 2, ent_section_y);

        /* ── Entity palette (bottom section) ── */
        {
            int label_h = FONT_H + 6;
            font_draw_text(r, "ENTITIES", px + 2, ent_section_y + (label_h - FONT_H) / 2, COL_TEXT);

            int pal_y_start = ent_section_y + label_h;
            int ent_area_h = py + ph - pal_y_start;
            SDL_Rect clip = {px, pal_y_start, EDITOR_PALETTE_W, ent_area_h};
            SDL_RenderSetClipRect(r, &clip);

            for (int i = 0; i < g_entity_registry.count; i++) {
                const EntityRegEntry *ent = &g_entity_registry.entries[i];
                int ey = pal_y_start + i * 12 - ed->entity_palette_scroll * 12;

                if (ey + 10 < pal_y_start || ey > py + ph) continue;

                /* Mini-icon (or color swatch fallback) */
                if (ent->icon >= 0 && ent->icon < ICON_COUNT) {
                    draw_icon(r, (EditorIcon)ent->icon,
                              px + 2, ey + 1, ent->color);
                } else {
                    SDL_SetRenderDrawColor(r, ent->color.r, ent->color.g,
                                           ent->color.b, 255);
                    SDL_Rect swatch = {px + 2, ey + 1, 8, 8};
                    SDL_RenderFillRect(r, &swatch);
                }

                /* Name */
                SDL_Color nc = (i == ed->selected_entity) ? COL_HIGHLIGHT : COL_TEXT;
                font_draw_text(r, ent->display, px + 13, ey + 2, nc);
            }

            SDL_RenderSetClipRect(r, NULL);
        }
    }

    /* ── Bottom status bar ── */
    {
        int sy = SCREEN_HEIGHT - EDITOR_STATUS_H;
        SDL_SetRenderDrawColor(r, COL_PANEL.r, COL_PANEL.g, COL_PANEL.b, COL_PANEL.a);
        SDL_Rect sb = {0, sy, SCREEN_WIDTH, EDITOR_STATUS_H};
        SDL_RenderFillRect(r, &sb);

        /* Cursor position */
        int mx_s, my_s;
        input_mouse_pos(&mx_s, &my_s);
        int tx = 0, ty = 0;
        screen_to_tile(ed, mx_s, my_s, &tx, &ty);

        char status[512];
        snprintf(status, sizeof(status), "%dx%d  (%d,%d)  Z:%.0f%%  %s%s",
                 ed->map.width, ed->map.height,
                 tx, ty,
                 ed->camera.zoom * 100.0f,
                 ed->has_file ? ed->file_path : "new level",
                 ed->dirty ? " *" : "");
        font_draw_text(r, status, 2, sy + (EDITOR_STATUS_H - FONT_H) / 2, COL_TEXT);
    }
}
