#pragma once

#include <algorithm>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include <nlohmann/json.hpp>
#include <imgui.h>

#include "../module/item_pricing.h"
#include "../util/Config.h"
#include "../util/logger.h"
#include "../util/module_paths.h"
#include "../gui/dxhook/dxhook.h"
#include "../util/svg_raster_map.h"
#include "../util/tile_raster_map.h"

namespace minimap
{
    using json = nlohmann::json;

    enum class map_style
    {
        auto_style = 0,
        abstract_style = 1,
        satellite_style = 2,
    };

    struct map_bounds
    {
        float min_x = 0.f;
        float max_x = 1.f;
        float min_z = 0.f;
        float max_z = 1.f;
    };

    struct map_area_rect
    {
        float min_x = 0.f;
        float max_x = 0.f;
        float min_z = 0.f;
        float max_z = 0.f;
        std::string label;
    };

    struct map_layer_extent
    {
        float height_min = -99999.f;
        float height_max = 99999.f;
        std::vector<map_area_rect> areas;
    };

    struct map_overlay_layer
    {
        std::string name;
        std::string zone;
        std::string svg_layer;
        std::string tile_path;
        std::vector<map_layer_extent> extents;
    };

    struct map_floor
    {
        float y_min = -999.f;
        float y_max = 999.f;
        std::string svg;
        std::string svg_layer;
        std::string projection;
        map_bounds bounds{};
    };

    struct map_view_transform
    {
        float rx = 0.f;
        float ry = 0.f;
        float size = 0.f;
        float cx = 0.f;
        float cy = 0.f;
        float map_w = 0.f;
        float map_h = 0.f;
        float map_x0 = 0.f;
        float map_y0 = 0.f;
        float rotation_rad = 0.f;
        bool rotate = false;
    };

    struct map_config
    {
        std::string id;
        std::vector<std::string> aliases;
        std::string svg;
        std::string svg_layer;
        std::string tile_path;
        int tile_size = 256;
        int min_zoom = 1;
        int max_zoom = 6;
        float transform[4]{1.f, 0.f, 0.395f, 0.f};
        std::string projection;
        int coordinate_rotation = 0;
        int abstract_coordinate_rotation = 0;
        std::string first_floor_layer = "First_Floor";
        std::vector<map_overlay_layer> overlay_layers;
        std::vector<map_floor> floors;
        map_bounds svg_bounds{};
        bool has_svg_bounds = false;
        std::string config_path;
    };

    struct map_style_calibration
    {
        float bounds_corner0_x = 0.f;
        float bounds_corner0_z = 0.f;
        float bounds_corner1_x = 0.f;
        float bounds_corner1_z = 0.f;
        float transform[4]{1.f, 0.f, 0.395f, 0.f};
        float rotation_deg = 0.f;
        float offset_x = 0.f;
        float offset_z = 0.f;
        int coordinate_rotation = 0;
    };

    struct map_editor_state
    {
        bool enabled = false;
        map_style preview_style = map_style::abstract_style;
        std::string location_id;
        int base_coordinate_rotation = 0;
        map_style_calibration abstract{};
        map_style_calibration satellite{};
        std::vector<map_overlay_layer> overlay_layers;
        int selected_overlay = 0;
        int selected_extent = 0;
        bool session_ready = false;
        bool changes_applied = false;
        map_style applied_preview_style = map_style::abstract_style;
        map_style_calibration applied_abstract{};
        map_style_calibration applied_satellite{};
        std::vector<map_overlay_layer> applied_overlay_layers;
        bool tiles_need_rebuild = false;
        bool show_zone_outlines = false;
        bool show_abstract_overlay = true;
        float abstract_overlay_opacity = 0.45f;
        bool minimap_calibrate = false;
    };

    struct loaded_map
    {
        map_config config;
        svg_raster_map::svg_document svg_doc;
        svg_raster_map::layer_cache layer_rasters;
        tile_raster_map::layer_cache tile_rasters;
        map_style active_style = map_style::auto_style;
        map_style resolved_base_style = map_style::abstract_style;
        std::string active_base_layer;
        std::string active_overlay_layer;
        std::string active_overlay_name;
        std::string active_zone_name;
        int active_overlay_index = -1;
        bool map_ready = false;
        float last_player_x = 0.f;
        float last_player_z = 0.f;
        bool has_last_player_pos = false;
    };

    map_editor_state& editor_state();
    void editor_export_exfil_coords();
    void editor_set_enabled(bool enabled);
    void editor_bind_config(const map_config& config);
    void editor_tick();
    void editor_apply_changes();
    void editor_apply_to_active(loaded_map& active);
    void editor_reload_from_active();
    void reload_map_configs_from_disk();
    bool editor_save_to_edit_folder(std::string& out_path);
    const map_style_calibration& editor_calibration_for_style(map_style style);
    map_style editor_resolve_draw_style();
    bool editor_is_active();
    bool ensure_svg_doc_loaded(loaded_map& active);
    bool editor_show_abstract_overlay();
    void draw_editor_abstract_overlay(ImDrawList* draw_list, loaded_map& active,
                                      const map_view_transform& view, int abstract_rotation,
                                      const map_bounds& source_bounds, float opacity_mul);

    inline std::mutex& cache_mutex()
    {
        static std::mutex m;
        return m;
    }

    inline std::recursive_mutex& map_load_mutex()
    {
        static std::recursive_mutex m;
        return m;
    }

    inline std::vector<map_config>& configs()
    {
        static std::vector<map_config> items;
        return items;
    }

    inline std::unordered_map<std::string, size_t>& alias_index()
    {
        static std::unordered_map<std::string, size_t> index;
        return index;
    }

    inline loaded_map& active_map()
    {
        static loaded_map map;
        return map;
    }

    inline std::string& last_status()
    {
        static std::string status;
        return status;
    }

    inline std::string& pending_map_location()
    {
        static std::string location;
        return location;
    }

    inline bool& pending_map_load()
    {
        static bool pending = false;
        return pending;
    }

    inline map_style& user_map_style()
    {
        static map_style style = map_style::auto_style;
        return style;
    }

    inline void set_map_style(const map_style style)
    {
        user_map_style() = style;
    }

    inline map_style get_map_style()
    {
        return user_map_style();
    }

    inline bool config_has_svg(const map_config& config)
    {
        return !config.svg.empty();
    }

    inline bool config_has_tile(const map_config& config)
    {
        return !config.tile_path.empty();
    }

    inline bool overlay_has_svg(const map_overlay_layer& layer)
    {
        return !layer.svg_layer.empty();
    }

    inline bool overlay_has_tile(const map_overlay_layer& layer)
    {
        return !layer.tile_path.empty();
    }

    inline map_style resolve_base_style(const map_config& config, const map_style requested)
    {
        const bool has_svg = config_has_svg(config);
        const bool has_tile = config_has_tile(config);

        if (requested == map_style::abstract_style)
        {
            if (has_svg)
                return map_style::abstract_style;
            if (has_tile)
                return map_style::satellite_style;
        }
        else if (requested == map_style::satellite_style)
        {
            if (has_tile)
                return map_style::satellite_style;
            if (has_svg)
                return map_style::abstract_style;
        }
        else
        {
            if (has_svg)
                return map_style::abstract_style;
            if (has_tile)
                return map_style::satellite_style;
        }

        return map_style::abstract_style;
    }

    inline bool overlay_use_svg(const map_style base_style, const map_overlay_layer& overlay)
    {
        const bool has_svg = overlay_has_svg(overlay);
        const bool has_tile = overlay_has_tile(overlay);
        if (!has_svg && !has_tile)
            return false;
        if (!has_svg)
            return false;
        if (!has_tile)
            return true;
        return base_style == map_style::abstract_style;
    }

    inline bool config_needs_svg_raster(const map_config& config, const map_style resolved_style)
    {
        if (!config_has_svg(config))
            return false;
        if (resolved_style == map_style::abstract_style)
            return true;

        for (const auto& overlay : config.overlay_layers)
        {
            if (overlay_use_svg(resolved_style, overlay))
                return true;
        }
        return false;
    }

    inline float& world_uv_offset_x()
    {
        static float value = 0.f;
        return value;
    }

    inline float& world_uv_offset_z()
    {
        static float value = 0.f;
        return value;
    }

    inline float& world_uv_rotation_extra_deg()
    {
        static float value = 0.f;
        return value;
    }

    inline ImVec2 rotate_uv_around_center(const ImVec2& uv, const float deg)
    {
        if (fabsf(deg) < 0.001f)
            return uv;

        const float rad = deg * (3.14159265f / 180.f);
        const float sin_r = sinf(rad);
        const float cos_r = cosf(rad);
        const float dx = uv.x - 0.5f;
        const float dy = uv.y - 0.5f;
        return {
            0.5f + dx * cos_r - dy * sin_r,
            0.5f + dx * sin_r + dy * cos_r
        };
    }

    inline ImVec2 map_tex_uv(const int rotation_deg, const float u, const float v)
    {
        ImVec2 uv = {u, v};
        switch ((rotation_deg % 360 + 360) % 360)
        {
        case 90: uv = {1.f - v, u}; break;
        case 180: uv = {1.f - u, 1.f - v}; break;
        case 270: uv = {v, 1.f - u}; break;
        default: break;
        }
        return rotate_uv_around_center(uv, world_uv_rotation_extra_deg());
    }

    inline std::string tile_path_layer_slug(const std::string& tile_path)
    {
        if (tile_path.empty())
            return "base";

        const size_t maps_pos = tile_path.find("/maps/");
        if (maps_pos != std::string::npos)
        {
            std::string rest = tile_path.substr(maps_pos + 6);
            const size_t z_token = rest.find("/{z}");
            if (z_token != std::string::npos)
                rest = rest.substr(0, z_token);

            if (!rest.empty())
            {
                const size_t last_slash = rest.rfind('/');
                return last_slash == std::string::npos ? rest : rest.substr(last_slash + 1);
            }
        }

        return "base";
    }

    inline tile_raster_map::tile_map_params make_tile_params(const map_config& config,
                                                             const std::string& tile_path,
                                                             const map_bounds& bounds)
    {
        tile_raster_map::tile_map_params params;
        params.map_id = config.id;
        params.layer_slug = tile_path_layer_slug(tile_path);
        params.tile_path_template = tile_path;
        params.tile_size = config.tile_size;
        params.min_zoom = config.min_zoom;
        params.max_zoom = config.max_zoom;
        params.transform[0] = config.transform[0];
        params.transform[1] = config.transform[1];
        params.transform[2] = config.transform[2];
        params.transform[3] = config.transform[3];
        params.coordinate_rotation = config.coordinate_rotation;
        params.bounds.min_x = bounds.min_x;
        params.bounds.max_x = bounds.max_x;
        params.bounds.min_z = bounds.min_z;
        params.bounds.max_z = bounds.max_z;
        return params;
    }

    inline bool bounds_from_area(const map_area_rect& area, map_bounds& out)
    {
        if (area.max_x <= area.min_x || area.max_z <= area.min_z)
            return false;

        constexpr float pad = 8.f;
        out.min_x = area.min_x - pad;
        out.max_x = area.max_x + pad;
        out.min_z = area.min_z - pad;
        out.max_z = area.max_z + pad;
        return true;
    }

    inline bool union_overlay_tile_bounds(const map_overlay_layer& overlay, map_bounds& out)
    {
        bool found = false;
        for (const auto& extent : overlay.extents)
        {
            for (const auto& area : extent.areas)
            {
                if (area.max_x <= area.min_x || area.max_z <= area.min_z)
                    continue;

                if (!found)
                {
                    out.min_x = area.min_x;
                    out.max_x = area.max_x;
                    out.min_z = area.min_z;
                    out.max_z = area.max_z;
                    found = true;
                }
                else
                {
                    out.min_x = (std::min)(out.min_x, area.min_x);
                    out.max_x = (std::max)(out.max_x, area.max_x);
                    out.min_z = (std::min)(out.min_z, area.min_z);
                    out.max_z = (std::max)(out.max_z, area.max_z);
                }
            }
        }

        if (!found || out.max_x <= out.min_x || out.max_z <= out.min_z)
            return false;

        constexpr float pad = 8.f;
        out.min_x -= pad;
        out.max_x += pad;
        out.min_z -= pad;
        out.max_z += pad;
        return true;
    }

    inline std::string overlay_tile_cache_key(const std::string& overlay_name, const map_bounds& bounds)
    {
        std::ostringstream key;
        key << "overlay:" << overlay_name << ':'
            << bounds.min_x << '|' << bounds.max_x << '|'
            << bounds.min_z << '|' << bounds.max_z;
        return key.str();
    }

    inline std::string& failed_load_config_id()
    {
        static std::string id;
        return id;
    }

    inline void clear_load_failure()
    {
        failed_load_config_id().clear();
    }

    inline std::string wide_to_utf8(const std::wstring& wide)
    {
        if (wide.empty()) return {};
        const int size = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, nullptr, 0, nullptr, nullptr);
        if (size <= 1) return {};
        std::string out(static_cast<size_t>(size - 1), '\0');
        WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, out.data(), size, nullptr, nullptr);
        return out;
    }

    inline std::filesystem::path get_self_module_dir_wide()
    {
        return module_paths::dll_directory_wide();
    }

    inline std::string path_to_utf8(const std::filesystem::path& path)
    {
#if defined(_MSC_VER)
        return wide_to_utf8(path.wstring());
#else
        return path.string();
#endif
    }

    inline std::string& active_maps_directory()
    {
        static std::string dir;
        return dir;
    }

    inline std::wstring utf8_to_wide(const std::string& text)
    {
        if (text.empty()) return {};
        const int size = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, nullptr, 0);
        if (size <= 1) return {};
        std::wstring wide(static_cast<size_t>(size - 1), L'\0');
        MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, wide.data(), size);
        return wide;
    }

    inline std::filesystem::path path_from_utf8(const std::string& text)
    {
#if defined(_MSC_VER)
        return std::filesystem::path(utf8_to_wide(text));
#else
        return std::filesystem::path(text);
#endif
    }

    inline std::string canonicalize_dir_string(const std::string& dir)
    {
        if (dir.empty()) return {};
        std::error_code ec;
        std::filesystem::path path = path_from_utf8(dir);
        if (path.is_relative())
            path = std::filesystem::absolute(path, ec);
        path = std::filesystem::weakly_canonical(path, ec);
        return path_to_utf8(path);
    }

    inline void add_unique_dir_string(std::vector<std::string>& dirs, const std::string& dir)
    {
        if (dir.empty()) return;

        const std::string abs = canonicalize_dir_string(dir);
        if (abs.empty()) return;
        if (std::find(dirs.begin(), dirs.end(), abs) == dirs.end())
            dirs.push_back(abs);
    }

    inline void add_bsg_launcher_maps_dirs(std::vector<std::string>& dirs, const std::string& start_dir)
    {
        if (start_dir.empty()) return;

        std::error_code ec;
        std::filesystem::path current = path_from_utf8(start_dir);
        current = std::filesystem::weakly_canonical(current, ec);

        for (int depth = 0; depth < 6 && !current.empty(); ++depth)
        {
            add_unique_dir_string(dirs, path_to_utf8(current / "BsgLauncher" / "maps"));
            add_unique_dir_string(dirs, path_to_utf8(current / "launcher" / "maps"));

            const std::filesystem::path parent = current.parent_path();
            if (!parent.empty() && parent != current)
            {
                add_unique_dir_string(dirs, path_to_utf8(parent / "BsgLauncher" / "maps"));
                add_unique_dir_string(dirs, path_to_utf8(parent / "launcher" / "maps"));
            }

            if (parent.empty() || parent == current)
                break;
            current = parent;
        }
    }

    inline std::vector<std::string> build_maps_search_directories()
    {
        std::vector<std::string> dirs;

        Configuration cfg;
        if (cfg.load("launcher.cfg"))
        {
            const std::string configured = item_pricing::trim_config_value(cfg.get("mapsDir"));
            if (!configured.empty())
                add_unique_dir_string(dirs, item_pricing::resolve_configured_path(configured));
        }

        const std::string dll_dir = item_pricing::get_module_directory("TarkovAddons.dll");
        const std::string game_dir = item_pricing::get_process_directory();
        const std::string appdata_dir = item_pricing::get_appdata_config_directory();

        if (!dll_dir.empty())
        {
            add_unique_dir_string(dirs, item_pricing::join_path(dll_dir, "maps"));
            add_unique_dir_string(dirs, dll_dir);
        }

        if (!appdata_dir.empty())
            add_unique_dir_string(dirs, item_pricing::join_path(appdata_dir, "maps"));

        if (!game_dir.empty())
        {
            add_bsg_launcher_maps_dirs(dirs, game_dir);
            add_unique_dir_string(dirs, item_pricing::join_path(game_dir, "maps"));
            add_unique_dir_string(dirs, item_pricing::join_path(game_dir, "TarkovAddons\\maps"));
            add_unique_dir_string(dirs, item_pricing::join_path(game_dir, "TarkovAddons"));
            add_unique_dir_string(dirs, item_pricing::join_path(game_dir, "..\\BsgLauncher\\maps"));
            add_unique_dir_string(dirs, item_pricing::join_path(game_dir, "..\\launcher\\maps"));
        }

        add_unique_dir_string(dirs, "maps");
        add_unique_dir_string(dirs, item_pricing::to_absolute_path("../BsgLauncher/maps"));
        add_unique_dir_string(dirs, item_pricing::to_absolute_path("../launcher/maps"));

        const std::filesystem::path self_dir = get_self_module_dir_wide();
        if (!self_dir.empty())
        {
            add_unique_dir_string(dirs, path_to_utf8(self_dir / L"maps"));
            add_unique_dir_string(dirs, path_to_utf8(self_dir));
        }

        return dirs;
    }

    inline std::vector<std::filesystem::path> collect_search_dirs()
    {
        std::vector<std::filesystem::path> dirs;
        for (const std::string& dir : build_maps_search_directories())
            dirs.push_back(path_from_utf8(dir));
        return dirs;
    }

    inline bool is_ignored_map_scan_file(const std::filesystem::path& path)
    {
        const std::string stem = path.stem().string();
        return stem == "tarkov_prices" || stem == "launcher";
    }

    inline bool is_known_map_asset_name(const std::string& stem)
    {
        static const char* k_names[] = {
            "bigmap", "factory", "ground-zero", "interchange", "labyrinth",
            "laboratory", "lighthouse", "reserve", "shoreline", "streets-of-tarkov",
            "terminal", "woods"
        };
        for (const char* name : k_names)
        {
            if (stem == name)
                return true;
        }
        return false;
    }

    inline bool is_map_asset_file(const std::filesystem::path& path)
    {
        if (!path.has_filename() || is_ignored_map_scan_file(path))
            return false;

        const std::string ext = path.extension().string();
        const std::string stem = path.stem().string();
        if (ext == ".svg")
            return is_known_map_asset_name(stem);

        if (ext == ".json")
            return is_known_map_asset_name(stem);

        return false;
    }

    inline bool maps_directory_has_data(const std::filesystem::path& dir)
    {
        std::error_code ec;
        if (!std::filesystem::is_directory(dir, ec))
            return false;

        for (const auto& entry : std::filesystem::directory_iterator(dir, ec))
        {
            if (ec || !entry.is_regular_file())
                continue;
            if (is_map_asset_file(entry.path()))
                return true;
        }

        return false;
    }

    inline std::string find_maps_directory()
    {
        if (!active_maps_directory().empty())
            return active_maps_directory();

        for (const std::string& dir : build_maps_search_directories())
        {
            if (!maps_directory_has_data(std::filesystem::path(dir)))
                continue;

            active_maps_directory() = dir;
            return dir;
        }

        const std::vector<std::string> dirs = build_maps_search_directories();
        return dirs.empty() ? std::string{} : dirs.front();
    }

    inline std::string get_maps_directory()
    {
        const std::string found = find_maps_directory();
        if (!found.empty())
            return found;

        const std::string dll_dir = item_pricing::get_module_directory("TarkovAddons.dll");
        if (!dll_dir.empty())
            return item_pricing::join_path(dll_dir, "maps");

        return item_pricing::join_path(item_pricing::get_process_directory(), "maps");
    }

    inline std::string format_maps_search_paths()
    {
        std::string paths;
        for (const std::string& dir : build_maps_search_directories())
        {
            if (!paths.empty())
                paths += "\n";
            paths += dir;
        }
        return paths;
    }

    inline std::string to_lower(std::string value)
    {
        std::transform(value.begin(), value.end(), value.begin(),
            [](const unsigned char c) { return static_cast<char>(std::tolower(c)); });
        return value;
    }

    inline map_bounds tarkov_customs_bounds()
    {
        // tarkov.dev maps.json customs bounds_corners: [[698,-307],[-372,237]]
        return { -372.f, 698.f, -307.f, 237.f };
    }

    inline map_config make_builtin_bigmap_config()
    {
        map_config cfg;
        cfg.id = "bigmap";
        cfg.svg = "bigmap.svg";
        cfg.svg_layer = "Ground_Level";
        cfg.projection = "tarkov";
        cfg.coordinate_rotation = 180;
        cfg.config_path = "<builtin>";
        cfg.aliases = { "bigmap", "customs", "customsplan" };

        map_floor floor;
        floor.svg = cfg.svg;
        floor.svg_layer = cfg.svg_layer;
        floor.projection = cfg.projection;
        floor.y_min = -999.f;
        floor.y_max = 999.f;
        floor.bounds = tarkov_customs_bounds();
        cfg.floors.push_back(floor);
        return cfg;
    }

    inline void ensure_builtin_configs(std::vector<map_config>& parsed, std::unordered_map<std::string, bool>& seen_ids)
    {
        map_config builtin = make_builtin_bigmap_config();
        const std::string key = to_lower(builtin.id);
        if (!key.empty() && !seen_ids[key])
        {
            seen_ids[key] = true;
            parsed.push_back(std::move(builtin));
        }
    }

    inline bool parse_bounds(const json& node, map_bounds& out)
    {
        if (!node.is_object()) return false;
        out.min_x = node.value("min_x", out.min_x);
        out.max_x = node.value("max_x", out.max_x);
        out.min_z = node.value("min_z", out.min_z);
        out.max_z = node.value("max_z", out.max_z);
        return out.max_x > out.min_x && out.max_z > out.min_z;
    }

    inline bool parse_bounds_corners(const json& corners, map_bounds& out)
    {
        if (!corners.is_array() || corners.size() < 2)
            return false;

        const auto& c0 = corners[0];
        const auto& c1 = corners[1];
        if (!c0.is_array() || c0.size() < 2 || !c1.is_array() || c1.size() < 2)
            return false;

        const float x0 = c0[0].get<float>();
        const float z0 = c0[1].get<float>();
        const float x1 = c1[0].get<float>();
        const float z1 = c1[1].get<float>();

        out.min_x = (x0 < x1) ? x0 : x1;
        out.max_x = (x0 > x1) ? x0 : x1;
        out.min_z = (z0 < z1) ? z0 : z1;
        out.max_z = (z0 > z1) ? z0 : z1;
        return out.max_x > out.min_x && out.max_z > out.min_z;
    }

    inline bool parse_area_rect(const json& node, map_area_rect& out)
    {
        if (node.is_object())
        {
            out.min_x = node.value("min_x", out.min_x);
            out.max_x = node.value("max_x", out.max_x);
            out.min_z = node.value("min_z", out.min_z);
            out.max_z = node.value("max_z", out.max_z);
            out.label = node.value("label", "");
            return out.max_x > out.min_x && out.max_z > out.min_z;
        }

        if (!node.is_array() || node.size() < 2)
            return false;

        const auto& c0 = node[0];
        const auto& c1 = node[1];
        if (!c0.is_array() || c0.size() < 2 || !c1.is_array() || c1.size() < 2)
            return false;

        const float x0 = c0[0].get<float>();
        const float z0 = c0[1].get<float>();
        const float x1 = c1[0].get<float>();
        const float z1 = c1[1].get<float>();
        out.min_x = (x0 < x1) ? x0 : x1;
        out.max_x = (x0 > x1) ? x0 : x1;
        out.min_z = (z0 < z1) ? z0 : z1;
        out.max_z = (z0 > z1) ? z0 : z1;
        if (node.size() >= 3 && node[2].is_string())
            out.label = node[2].get<std::string>();
        return out.max_x > out.min_x && out.max_z > out.min_z;
    }

    inline bool parse_layer_extent(const json& node, map_layer_extent& out)
    {
        if (!node.is_object())
            return false;

        if (node.contains("height") && node["height"].is_array() && node["height"].size() >= 2)
        {
            out.height_min = node["height"][0].get<float>();
            out.height_max = node["height"][1].get<float>();
        }
        else
        {
            out.height_min = node.value("height_min", out.height_min);
            out.height_max = node.value("height_max", out.height_max);
        }

        out.areas.clear();
        if (node.contains("areas") && node["areas"].is_array())
        {
            for (const auto& area_node : node["areas"])
            {
                map_area_rect area;
                if (parse_area_rect(area_node, area))
                    out.areas.push_back(std::move(area));
            }
        }
        else if (node.contains("bounds") && node["bounds"].is_array())
        {
            for (const auto& area_node : node["bounds"])
            {
                map_area_rect area;
                if (parse_area_rect(area_node, area))
                    out.areas.push_back(std::move(area));
            }
        }

        return out.height_max >= out.height_min;
    }

    inline bool parse_overlay_layer(const json& node, map_overlay_layer& out)
    {
        if (!node.is_object())
            return false;

        out.name = node.value("name", "");
        out.zone = node.value("zone", "");
        out.svg_layer = node.value("svg_layer", "");
        if (out.svg_layer.empty())
            out.svg_layer = node.value("svgLayer", "");
        out.tile_path = node.value("tile_path", "");
        if (out.tile_path.empty())
            out.tile_path = node.value("tilePath", "");
        if (out.svg_layer.empty() && !out.tile_path.empty())
            out.svg_layer.clear();
        else if (out.svg_layer.empty())
            out.svg_layer = out.name;

        out.extents.clear();
        if (node.contains("extents") && node["extents"].is_array())
        {
            for (const auto& extent_node : node["extents"])
            {
                map_layer_extent extent;
                if (parse_layer_extent(extent_node, extent))
                    out.extents.push_back(std::move(extent));
            }
        }

        return !out.name.empty() && !out.extents.empty()
            && (overlay_has_svg(out) || overlay_has_tile(out));
    }

    inline bool point_in_area(const map_area_rect& area, const float world_x, const float world_z)
    {
        return world_x >= area.min_x && world_x <= area.max_x
            && world_z >= area.min_z && world_z <= area.max_z;
    }

    inline bool pick_overlay_tile_bounds(const map_overlay_layer& overlay, const float world_x,
                                         const float world_z, map_bounds& out)
    {
        bool found = false;
        float best_area = 0.f;

        for (const auto& extent : overlay.extents)
        {
            for (const auto& area : extent.areas)
            {
                if (!point_in_area(area, world_x, world_z))
                    continue;

                const float size = (area.max_x - area.min_x) * (area.max_z - area.min_z);
                if (!found || size < best_area)
                {
                    if (!bounds_from_area(area, out))
                        continue;
                    found = true;
                    best_area = size;
                }
            }
        }

        if (found)
            return true;

        return union_overlay_tile_bounds(overlay, out);
    }

    inline int score_layer_extent(const map_layer_extent& extent, const float world_x,
                                  const float world_y, const float world_z)
    {
        if (world_y < extent.height_min || world_y > extent.height_max)
            return -1;

        if (extent.areas.empty())
            return 1;

        for (const auto& area : extent.areas)
        {
            if (point_in_area(area, world_x, world_z))
                return 100;
        }
        return -1;
    }

    inline bool is_underground_overlay(const map_overlay_layer& layer)
    {
        if (layer.svg_layer == "Underground_Level" || layer.svg_layer == "Bunkers"
            || layer.svg_layer == "Basement")
            return true;

        return layer.name == "Underground" || layer.name == "Bunkers" || layer.name == "Technical"
            || layer.name == "Garage" || layer.name == "Tunnels";
    }

    inline int pick_overlay_layer_index(const map_config& config, const float world_x, const float world_y,
                                        const float world_z)
    {
        int best_index = -1;
        int best_score = -1;
        float best_height_span = 1e30f;

        for (size_t i = 0; i < config.overlay_layers.size(); ++i)
        {
            const auto& layer = config.overlay_layers[i];
            for (const auto& extent : layer.extents)
            {
                const int score = score_layer_extent(extent, world_x, world_y, world_z);
                if (score < 0)
                    continue;

                const float height_span = extent.height_max - extent.height_min;
                if (score > best_score || (score == best_score && height_span < best_height_span))
                {
                    best_score = score;
                    best_height_span = height_span;
                    best_index = static_cast<int>(i);
                }
            }
        }

        return best_index;
    }

    inline bool extent_covers_xz(const map_layer_extent& extent, const float world_x, const float world_z)
    {
        if (extent.areas.empty())
            return true;

        for (const auto& area : extent.areas)
        {
            if (point_in_area(area, world_x, world_z))
                return true;
        }
        return false;
    }

    inline bool has_upper_floors_above(const map_config& config, const float world_x, const float world_y,
                                       const float world_z)
    {
        for (const auto& layer : config.overlay_layers)
        {
            if (is_underground_overlay(layer))
                continue;

            for (const auto& extent : layer.extents)
            {
                if (!extent_covers_xz(extent, world_x, world_z))
                    continue;

                if (world_y < extent.height_min)
                    return true;
            }
        }
        return false;
    }

    inline bool has_underground_below(const map_config& config, const float world_x, const float world_y,
                                      const float world_z)
    {
        for (const auto& layer : config.overlay_layers)
        {
            if (!is_underground_overlay(layer))
                continue;

            for (const auto& extent : layer.extents)
            {
                if (!extent_covers_xz(extent, world_x, world_z))
                    continue;

                if (world_y > extent.height_max)
                    return true;
            }
        }
        return false;
    }

    inline std::string pick_surface_zone_label(const map_config& config, const float world_x,
                                               const float world_z)
    {
        std::string best_label;
        float best_area = 0.f;
        bool found = false;

        for (const auto& layer : config.overlay_layers)
        {
            if (is_underground_overlay(layer))
                continue;

            for (const auto& extent : layer.extents)
            {
                for (const auto& area : extent.areas)
                {
                    if (area.label.empty() || !point_in_area(area, world_x, world_z))
                        continue;

                    const float size = (area.max_x - area.min_x) * (area.max_z - area.min_z);
                    if (!found || size < best_area)
                    {
                        found = true;
                        best_area = size;
                        best_label = area.label;
                    }
                }
            }
        }

        return best_label;
    }

    inline std::string pick_underground_zone_label(const map_config& config, const float world_x,
                                                   const float world_y, const float world_z)
    {
        std::string best_label;
        float best_height_span = 1e30f;

        for (const auto& layer : config.overlay_layers)
        {
            if (!is_underground_overlay(layer))
                continue;

            for (const auto& extent : layer.extents)
            {
                if (world_y < extent.height_min || world_y > extent.height_max)
                    continue;

                const float height_span = extent.height_max - extent.height_min;
                for (const auto& area : extent.areas)
                {
                    if (area.label.empty() || !point_in_area(area, world_x, world_z))
                        continue;

                    if (height_span < best_height_span)
                    {
                        best_height_span = height_span;
                        best_label = area.label;
                    }
                }
            }
        }

        return best_label;
    }

    inline std::string pick_active_zone_label(const map_config& config, const float world_x,
                                              const float world_y, const float world_z,
                                              const int active_overlay_index)
    {
        if (active_overlay_index >= 0)
        {
            const map_overlay_layer& overlay =
                config.overlay_layers[static_cast<size_t>(active_overlay_index)];
            if (is_underground_overlay(overlay))
                return pick_underground_zone_label(config, world_x, world_y, world_z);
            return pick_surface_zone_label(config, world_x, world_z);
        }

        if (has_upper_floors_above(config, world_x, world_y, world_z))
            return pick_surface_zone_label(config, world_x, world_z);

        if (has_underground_below(config, world_x, world_y, world_z))
            return {};

        return pick_surface_zone_label(config, world_x, world_z);
    }

    inline const map_overlay_layer* pick_overlay_layer(const map_config& config,
                                                       const float world_x, const float world_y,
                                                       const float world_z)
    {
        const int index = pick_overlay_layer_index(config, world_x, world_y, world_z);
        if (index < 0)
            return nullptr;
        return &config.overlay_layers[static_cast<size_t>(index)];
    }

    inline void apply_projection_defaults(map_floor& floor, const map_config& config)
    {
        if (config.config_path != "<builtin>")
            return;

        const std::string projection = !floor.projection.empty() ? floor.projection : config.projection;
        if (projection == "tarkov" || projection == "tarkov.dev")
            floor.bounds = tarkov_customs_bounds();
    }

    inline bool parse_floor(const json& node, map_floor& out)
    {
        if (!node.is_object()) return false;

        out.y_min = node.value("y_min", out.y_min);
        out.y_max = node.value("y_max", out.y_max);
        out.svg = node.value("svg", "");
        out.svg_layer = node.value("svg_layer", "");
        out.projection = node.value("projection", "");

        if (node.contains("bounds_corners"))
        {
            if (!parse_bounds_corners(node["bounds_corners"], out.bounds))
                return false;
        }
        else if (!parse_bounds(node.value("bounds", json::object()), out.bounds))
        {
            return false;
        }

        return true;
    }

    inline bool parse_config_file(const std::string& path, map_config& out)
    {
        std::ifstream input(path);
        if (!input.is_open()) return false;

        json root;
        try
        {
            input >> root;
        }
        catch (...)
        {
            return false;
        }

        if (!root.is_object()) return false;

        out.id = root.value("id", "");
        out.svg = root.value("svg", "");
        out.svg_layer = root.value("svg_layer", "Ground_Level");
        out.tile_path = root.value("tile_path", "");
        if (out.tile_path.empty())
            out.tile_path = root.value("tilePath", "");
        out.tile_size = root.value("tile_size", root.value("tileSize", 256));
        out.min_zoom = root.value("min_zoom", root.value("minZoom", 1));
        out.max_zoom = root.value("max_zoom", root.value("maxZoom", 6));
        if (root.contains("transform") && root["transform"].is_array() && root["transform"].size() >= 4)
        {
            out.transform[0] = root["transform"][0].get<float>();
            out.transform[1] = root["transform"][1].get<float>();
            out.transform[2] = root["transform"][2].get<float>();
            out.transform[3] = root["transform"][3].get<float>();
        }
        out.projection = root.value("projection", "");
        out.coordinate_rotation = root.value("coordinate_rotation", root.value("coordinateRotation", 0));
        out.abstract_coordinate_rotation = root.value("abstract_coordinate_rotation",
            root.value("abstractCoordinateRotation", 0));
        out.first_floor_layer = root.value("first_floor_layer", "First_Floor");
        out.config_path = path;
        out.aliases.clear();
        out.floors.clear();
        out.overlay_layers.clear();

        if (root.contains("overlay_layers") && root["overlay_layers"].is_array())
        {
            for (const auto& layer_node : root["overlay_layers"])
            {
                map_overlay_layer layer;
                if (parse_overlay_layer(layer_node, layer))
                    out.overlay_layers.push_back(std::move(layer));
            }
        }
        else if (root.contains("layers") && root["layers"].is_array())
        {
            for (const auto& layer_node : root["layers"])
            {
                map_overlay_layer layer;
                if (parse_overlay_layer(layer_node, layer))
                    out.overlay_layers.push_back(std::move(layer));
            }
        }

        if (out.id.empty() || (out.svg.empty() && out.tile_path.empty()))
            return false;

        if (root.contains("aliases") && root["aliases"].is_array())
        {
            for (const auto& alias : root["aliases"])
            {
                if (alias.is_string())
                    out.aliases.push_back(alias.get<std::string>());
            }
        }
        out.aliases.push_back(out.id);
        out.has_svg_bounds = false;
        if (root.contains("svg_bounds_corners") && root["svg_bounds_corners"].is_array())
            out.has_svg_bounds = parse_bounds_corners(root["svg_bounds_corners"], out.svg_bounds);
        else if (root.contains("svgBounds") && root["svgBounds"].is_array())
            out.has_svg_bounds = parse_bounds_corners(root["svgBounds"], out.svg_bounds);

        if (root.contains("floors") && root["floors"].is_array() && !root["floors"].empty())
        {
            for (const auto& floor_node : root["floors"])
            {
                map_floor floor;
                if (parse_floor(floor_node, floor))
                    out.floors.push_back(floor);
            }
        }
        else
        {
            map_floor floor;
            floor.svg = out.svg;
            floor.svg_layer = out.svg_layer;
            floor.projection = out.projection;

            if (root.contains("bounds_corners"))
            {
                if (!parse_bounds_corners(root["bounds_corners"], floor.bounds))
                    return false;
            }
            else if (!parse_bounds(root.value("bounds", json::object()), floor.bounds))
            {
                return false;
            }
            out.floors.push_back(floor);
        }

        const std::string projection = out.projection;
        if (out.coordinate_rotation == 0
            && (projection == "tarkov" || projection == "tarkov.dev"))
            out.coordinate_rotation = 180;

        for (auto& floor : out.floors)
        {
            if (floor.svg.empty())
                floor.svg = out.svg;
            if (floor.svg_layer.empty())
                floor.svg_layer = out.svg_layer;
            if (floor.projection.empty())
                floor.projection = out.projection;
            apply_projection_defaults(floor, out);
        }

        return !out.floors.empty();
    }

    inline std::string resolve_map_path(const std::string& relative, const std::string& config_path)
    {
        if (relative.empty()) return {};

        std::error_code ec;
        const std::filesystem::path rel = path_from_utf8(relative);
        if (rel.is_absolute() && std::filesystem::exists(rel, ec))
            return path_to_utf8(std::filesystem::absolute(rel, ec));

        std::vector<std::filesystem::path> candidates;

        if (config_path != "<builtin>")
        {
            const std::filesystem::path cfg = path_from_utf8(config_path);
            const std::filesystem::path cfg_dir = cfg.parent_path();
            if (!cfg_dir.empty())
            {
                candidates.push_back(cfg_dir / rel);
                candidates.push_back(cfg_dir / rel.filename());
            }
        }

        for (const auto& dir : collect_search_dirs())
        {
            candidates.push_back(dir / rel);
            candidates.push_back(dir / rel.filename());
        }

        for (const auto& candidate : candidates)
        {
            if (candidate.empty()) continue;
            if (std::filesystem::exists(candidate, ec))
                return path_to_utf8(std::filesystem::absolute(candidate, ec));
        }

        return {};
    }

    inline void rebuild_alias_index()
    {
        alias_index().clear();
        for (size_t i = 0; i < configs().size(); ++i)
        {
            for (const auto& alias : configs()[i].aliases)
            {
                const std::string key = to_lower(alias);
                if (!key.empty())
                    alias_index()[key] = i;
            }
        }
    }

    inline void try_add_config_file(const std::string& path,
        std::vector<map_config>& parsed, std::unordered_map<std::string, bool>& seen_ids)
    {
        const std::filesystem::path file_path(path);
        if (!is_map_asset_file(file_path) || file_path.extension() != ".json")
            return;

        map_config cfg;
        if (!parse_config_file(path, cfg)) return;

        const std::string key = to_lower(cfg.id);
        if (key.empty() || seen_ids[key]) return;

        seen_ids[key] = true;
        parsed.push_back(std::move(cfg));
    }

    inline void scan_configs()
    {
        clear_load_failure();
        active_maps_directory().clear();

        std::vector<map_config> parsed;
        std::unordered_map<std::string, bool> seen_ids;

        for (const auto& dir : collect_search_dirs())
        {
            std::error_code ec;
            if (!std::filesystem::is_directory(dir, ec)) continue;

            for (const auto& entry : std::filesystem::directory_iterator(dir, ec))
            {
                if (ec || !entry.is_regular_file()) continue;
                if (entry.path().extension() != ".json") continue;
                try_add_config_file(path_to_utf8(entry.path()), parsed, seen_ids);
            }
        }

        ensure_builtin_configs(parsed, seen_ids);

        {
            std::lock_guard<std::mutex> lock(cache_mutex());
            configs() = std::move(parsed);
            rebuild_alias_index();
        }

        const std::string maps_dir = find_maps_directory();
        last_status() = "configs=" + std::to_string(configs().size())
            + " dir=" + maps_dir
            + " mod=" + path_to_utf8(get_self_module_dir_wide());
        logger::info("Minimap scan: " + last_status());
        if (configs().empty())
            logger::warn("Minimap files not found. Searched paths:\n" + format_maps_search_paths());
    }

    inline size_t config_count()
    {
        std::lock_guard<std::mutex> lock(cache_mutex());
        return configs().size();
    }

    inline const map_config* find_config(const std::string& location_id)
    {
        std::lock_guard<std::mutex> lock(cache_mutex());

        if (!location_id.empty())
        {
            const auto it = alias_index().find(to_lower(location_id));
            if (it != alias_index().end() && it->second < configs().size())
                return &configs()[it->second];
        }

        if (configs().size() == 1)
            return &configs().front();

        return nullptr;
    }

    inline const map_floor* pick_floor(const map_config& config, const float world_y)
    {
        for (const auto& floor : config.floors)
        {
            if (world_y >= floor.y_min && world_y <= floor.y_max)
                return &floor;
        }
        return config.floors.empty() ? nullptr : &config.floors.front();
    }

    inline const map_bounds& display_bounds(const map_config& config, const map_floor& floor,
                                            const map_style style)
    {
        if (style == map_style::abstract_style && config.has_svg_bounds)
            return config.svg_bounds;
        return floor.bounds;
    }

    inline map_style resolved_display_style(const loaded_map& active)
    {
        if (editor_is_active())
            return editor_resolve_draw_style();
        return active.resolved_base_style;
    }

    inline const map_bounds& display_bounds_for_active(const loaded_map& active, const map_floor& floor)
    {
        return display_bounds(active.config, floor, resolved_display_style(active));
    }

    inline int resolve_coordinate_rotation(const map_config& config, const map_style style,
                                            const map_style_calibration* editor_cal = nullptr)
    {
        if (editor_cal && editor_cal->coordinate_rotation != 0)
            return editor_cal->coordinate_rotation;

        if (style == map_style::satellite_style)
            return config.coordinate_rotation;

        if (config.abstract_coordinate_rotation != 0)
            return config.abstract_coordinate_rotation;

        return config.coordinate_rotation;
    }

    inline map_bounds bounds_from_calibration(const map_style_calibration& cal)
    {
        map_bounds out{};
        out.min_x = (std::min)(cal.bounds_corner0_x, cal.bounds_corner1_x);
        out.max_x = (std::max)(cal.bounds_corner0_x, cal.bounds_corner1_x);
        out.min_z = (std::min)(cal.bounds_corner0_z, cal.bounds_corner1_z);
        out.max_z = (std::max)(cal.bounds_corner0_z, cal.bounds_corner1_z);
        return out;
    }

    inline bool world_to_uv_calibrated(const map_style_calibration& cal, const float world_x, const float world_z,
                                       float& out_u, float& out_v)
    {
        const map_bounds b = bounds_from_calibration(cal);
        if (b.max_x <= b.min_x || b.max_z <= b.min_z)
            return false;

        float wx = world_x - cal.offset_x;
        float wz = world_z - cal.offset_z;

        if (fabsf(cal.rotation_deg) > 0.001f)
        {
            const float cx = (b.min_x + b.max_x) * 0.5f;
            const float cz = (b.min_z + b.max_z) * 0.5f;
            const float rad = -cal.rotation_deg * (3.14159265f / 180.f);
            const float sin_r = sinf(rad);
            const float cos_r = cosf(rad);
            const float dx = wx - cx;
            const float dz = wz - cz;
            wx = dx * cos_r - dz * sin_r + cx;
            wz = dx * sin_r + dz * cos_r + cz;
        }

        out_u = (wx - b.min_x) / (b.max_x - b.min_x);
        out_v = 1.f - (wz - b.min_z) / (b.max_z - b.min_z);
        return true;
    }

    inline bool world_to_uv(const map_bounds& bounds, const float world_x, const float world_z,
                            float& out_u, float& out_v)
    {
        const map_bounds& b = bounds;
        if (b.max_x <= b.min_x || b.max_z <= b.min_z)
            return false;

        float wx = world_x - world_uv_offset_x();
        float wz = world_z - world_uv_offset_z();

        const float extra_deg = world_uv_rotation_extra_deg();
        if (fabsf(extra_deg) > 0.001f)
        {
            const float cx = (b.min_x + b.max_x) * 0.5f;
            const float cz = (b.min_z + b.max_z) * 0.5f;
            const float rad = -extra_deg * (3.14159265f / 180.f);
            const float sin_r = sinf(rad);
            const float cos_r = cosf(rad);
            const float dx = wx - cx;
            const float dz = wz - cz;
            wx = dx * cos_r - dz * sin_r + cx;
            wz = dx * sin_r + dz * cos_r + cz;
        }

        out_u = (wx - b.min_x) / (b.max_x - b.min_x);
        out_v = 1.f - (wz - b.min_z) / (b.max_z - b.min_z);
        return true;
    }

    inline bool world_to_uv(const map_floor& floor, const float world_x, const float world_z,
                            float& out_u, float& out_v)
    {
        return world_to_uv(floor.bounds, world_x, world_z, out_u, out_v);
    }

    inline ImVec2 rotate_around(const ImVec2& point, const ImVec2& center, const float rad)
    {
        if (fabsf(rad) < 0.0001f)
            return point;

        const float sin_r = sinf(rad);
        const float cos_r = cosf(rad);
        const float dx = point.x - center.x;
        const float dy = point.y - center.y;
        return {
            center.x + dx * cos_r - dy * sin_r,
            center.y + dx * sin_r + dy * cos_r
        };
    }

    inline bool build_map_view(const map_bounds& bounds, const uint32_t tex_w, const uint32_t tex_h,
                               const float rx, const float ry, const float size,
                               const float center_u, const float center_v, const float radar_range,
                               const float view_yaw_deg, const bool rotate_with_view,
                               map_view_transform& out)
    {
        if (tex_w == 0 || tex_h == 0 || size <= 1.f || radar_range <= 1.f)
            return false;

        const map_bounds& b = bounds;
        const float world_w = b.max_x - b.min_x;
        const float world_h = b.max_z - b.min_z;
        if (world_w <= 0.f || world_h <= 0.f)
            return false;

        // radar_range = world meters from center to minimap edge (same as classic radar)
        const float half = size * 0.5f;
        const float map_w = half * world_w / radar_range;
        const float map_h = half * world_h / radar_range;

        out.rx = rx;
        out.ry = ry;
        out.size = size;
        out.cx = rx + size * 0.5f;
        out.cy = ry + size * 0.5f;
        out.map_w = map_w;
        out.map_h = map_h;
        out.map_x0 = out.cx - center_u * map_w;
        out.map_y0 = out.cy - center_v * map_h;
        out.rotate = rotate_with_view;
        out.rotation_rad = rotate_with_view ? -view_yaw_deg * (3.14159265f / 180.f) : 0.f;
        return true;
    }

    inline bool build_map_view(const map_floor& floor, const uint32_t tex_w, const uint32_t tex_h,
                               const float rx, const float ry, const float size,
                               const float center_u, const float center_v, const float radar_range,
                               const float view_yaw_deg, const bool rotate_with_view,
                               map_view_transform& out)
    {
        return build_map_view(floor.bounds, tex_w, tex_h, rx, ry, size, center_u, center_v, radar_range,
            view_yaw_deg, rotate_with_view, out);
    }

    inline ImVec2 map_uv_to_screen(const map_view_transform& view, const float u, const float v)
    {
        ImVec2 point = {
            view.map_x0 + u * view.map_w,
            view.map_y0 + v * view.map_h
        };
        if (view.rotate)
            point = rotate_around(point, {view.cx, view.cy}, view.rotation_rad);
        return point;
    }

    inline ImVec2 screen_to_map_uv(const map_view_transform& view, const ImVec2& screen)
    {
        ImVec2 point = screen;
        if (view.rotate)
            point = rotate_around(point, {view.cx, view.cy}, -view.rotation_rad);

        return {
            (point.x - view.map_x0) / (std::max)(view.map_w, 1.f),
            (point.y - view.map_y0) / (std::max)(view.map_h, 1.f)
        };
    }

    inline bool world_to_map_point(const map_bounds& bounds, const float world_x, const float world_z,
                                   const map_view_transform& view, ImVec2& out)
    {
        float u = 0.f;
        float v = 0.f;
        if (!world_to_uv(bounds, world_x, world_z, u, v))
            return false;

        out = map_uv_to_screen(view, u, v);
        return true;
    }

    inline bool world_to_map_point(const map_floor& floor, const float world_x, const float world_z,
                                   const map_view_transform& view, ImVec2& out)
    {
        return world_to_map_point(floor.bounds, world_x, world_z, view, out);
    }

    inline const svg_raster_map::raster_layer* find_layer_raster(const loaded_map& active,
                                                                 const std::string& layer_id)
    {
        if (layer_id.empty())
            return nullptr;

        const auto it = active.layer_rasters.find(layer_id);
        if (it == active.layer_rasters.end() || it->second.tex_id == ImTextureID_Invalid)
            return nullptr;
        return &it->second;
    }

    inline bool is_map_ready()
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        return active_map().map_ready;
    }

    inline void release_active_map()
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        loaded_map& active = active_map();
        svg_raster_map::release_layer_cache(active.layer_rasters);
        svg_raster_map::release_svg_document(active.svg_doc);
        tile_raster_map::release_layer_cache(active.tile_rasters);
        active.active_base_layer.clear();
        active.active_overlay_layer.clear();
        active.active_overlay_name.clear();
        active.active_zone_name.clear();
        active.active_overlay_index = -1;
        active.active_style = map_style::auto_style;
        active.resolved_base_style = map_style::abstract_style;
        active.map_ready = false;
        active.config = {};
        active.last_player_x = 0.f;
        active.last_player_z = 0.f;
        active.has_last_player_pos = false;
    }

    inline const map_floor* primary_floor(const map_config& config)
    {
        return config.floors.empty() ? nullptr : &config.floors.front();
    }

    inline bool ensure_tile_layer(loaded_map& active, const std::string& key, const std::string& tile_path,
                                  const map_bounds* stitch_bounds = nullptr)
    {
        const map_floor* floor = primary_floor(active.config);
        if (!floor || tile_path.empty())
            return false;

        const map_bounds& bounds = stitch_bounds ? *stitch_bounds : floor->bounds;
        return tile_raster_map::ensure_stitched_layer(
            active.tile_rasters,
            key,
            make_tile_params(active.config, tile_path, bounds));
    }

    inline bool ensure_overlay_tile_layer(loaded_map& active, const map_overlay_layer& overlay)
    {
        const map_floor* floor = primary_floor(active.config);
        if (!floor || overlay.tile_path.empty())
            return false;

        map_bounds bounds{};
        if (active.has_last_player_pos)
        {
            if (!pick_overlay_tile_bounds(overlay, active.last_player_x, active.last_player_z, bounds))
                bounds = floor->bounds;
        }
        else if (!union_overlay_tile_bounds(overlay, bounds))
        {
            bounds = floor->bounds;
        }

        const std::string key = overlay_tile_cache_key(overlay.name, bounds);
        return ensure_tile_layer(active, key, overlay.tile_path, &bounds);
    }

    inline bool open_svg_for_map(const map_config& config, svg_raster_map::svg_document& out_doc,
                                 svg_raster_map::layer_cache& out_layers)
    {
        const map_floor* floor = primary_floor(config);
        if (!floor)
            return false;

        const std::string svg_rel = !config.svg.empty() ? config.svg
            : (!floor->svg.empty() ? floor->svg : std::string());
        if (svg_rel.empty())
        {
            logger::error("Minimap config has no svg path");
            return false;
        }

        std::error_code ec;
        const std::string svg_path = resolve_map_path(svg_rel, config.config_path);
        if (svg_path.empty() || !std::filesystem::exists(path_from_utf8(svg_path), ec))
        {
            logger::error("Minimap SVG not found: " + svg_path);
            logger::warn("Minimap searched paths:\n" + format_maps_search_paths());
            return false;
        }

        active_maps_directory() = path_to_utf8(std::filesystem::path(svg_path).parent_path());

        svg_raster_map::release_layer_cache(out_layers);
        svg_raster_map::release_svg_document(out_doc);

        const map_bounds& raster_bounds = config.has_svg_bounds ? config.svg_bounds : floor->bounds;
        const float world_span = (std::max)(raster_bounds.max_x - raster_bounds.min_x,
            raster_bounds.max_z - raster_bounds.min_z);
        return svg_raster_map::open_svg_document(svg_path, world_span, out_doc);
    }

    inline bool load_raster_layers(const map_config& config, svg_raster_map::svg_document& out_doc,
                                   svg_raster_map::layer_cache& out_layers)
    {
        return open_svg_for_map(config, out_doc, out_layers);
    }

    inline bool ensure_svg_base_layer_rasterized(loaded_map& active)
    {
        if (!active.svg_doc.valid())
            return false;

        const std::string& base_layer = active.config.svg_layer.empty()
            ? std::string("Ground_Level") : active.config.svg_layer;
        const uint32_t default_w = svg_raster_map::default_raster_width(active.svg_doc);
        if (!svg_raster_map::ensure_layer_rasterized(active.svg_doc, active.layer_rasters, base_layer, default_w))
        {
            logger::error("Minimap SVG base layer rasterize failed: " + base_layer);
            return false;
        }

        return true;
    }

    inline bool ensure_svg_doc_open(loaded_map& active)
    {
        if (active.svg_doc.valid())
            return true;
        if (!config_has_svg(active.config))
            return false;
        return open_svg_for_map(active.config, active.svg_doc, active.layer_rasters);
    }

    inline void prepare_base_map_texture(loaded_map& active)
    {
        if (!active.map_ready)
            return;

        if (active.resolved_base_style == map_style::satellite_style && config_has_tile(active.config))
            ensure_tile_layer(active, "base", active.config.tile_path);
        else if (active.resolved_base_style == map_style::abstract_style)
        {
            if (ensure_svg_doc_open(active))
                ensure_svg_base_layer_rasterized(active);
        }
    }

    inline const svg_raster_map::raster_layer* find_drawable_layer(const loaded_map& active)
    {
        if (active.resolved_base_style == map_style::satellite_style)
        {
            const auto tile_it = active.tile_rasters.find("base");
            if (tile_it != active.tile_rasters.end()
                && tile_it->second.tex_id != ImTextureID_Invalid
                && tile_it->second.width > 0.f)
                return &tile_it->second;
            return nullptr;
        }

        if (const svg_raster_map::raster_layer* base = find_layer_raster(active, active.active_base_layer))
            return base;

        for (const auto& entry : active.layer_rasters)
        {
            if (entry.second.tex_id != ImTextureID_Invalid)
                return &entry.second;
        }

        const auto tile_it = active.tile_rasters.find("base");
        if (tile_it != active.tile_rasters.end() && tile_it->second.tex_id != ImTextureID_Invalid)
            return &tile_it->second;

        return nullptr;
    }

    inline bool get_map_size(uint32_t& out_w, uint32_t& out_h)
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        loaded_map& active = active_map();
        if (!active.map_ready)
            return false;

        prepare_base_map_texture(active);

        const svg_raster_map::raster_layer* layer = find_drawable_layer(active);
        if (layer && layer->width > 0.f && layer->height > 0.f)
        {
            out_w = static_cast<uint32_t>(layer->width + 0.5f);
            out_h = static_cast<uint32_t>(layer->height + 0.5f);
            return out_w > 0 && out_h > 0;
        }

        // Satellite tiles may still be stitching asynchronously; keep minimap usable.
        if (active.resolved_base_style == map_style::satellite_style && config_has_tile(active.config))
        {
            const map_floor* floor = primary_floor(active.config);
            if (floor)
            {
                const float world_w = floor->bounds.max_x - floor->bounds.min_x;
                const float world_h = floor->bounds.max_z - floor->bounds.min_z;
                if (world_w > 0.f && world_h > 0.f)
                {
                    constexpr uint32_t k_fallback_h = 2048;
                    out_h = k_fallback_h;
                    out_w = static_cast<uint32_t>((world_w / world_h) * static_cast<float>(k_fallback_h) + 0.5f);
                    if (out_w > 0)
                        return true;
                }
            }
        }

        return false;
    }

    inline map_style map_load_request_style()
    {
        return get_map_style();
    }

    inline uint32_t svg_target_raster_width(const map_view_transform& view, const svg_raster_map::svg_document& doc,
                                              const svg_raster_map::layer_cache& cache, const std::string& layer_id)
    {
        constexpr uint32_t max_tex_dim = 8192;
        const uint32_t default_w = svg_raster_map::default_raster_width(doc);

        const auto it = cache.find(layer_id);
        if (it != cache.end() && it->second.tex_id != ImTextureID_Invalid && it->second.cached_target_w > 0)
            return it->second.cached_target_w;

        const uint32_t zoom_w = static_cast<uint32_t>((std::max)(view.map_w, view.map_h) + 0.5f);
        return (std::min)(max_tex_dim, (std::max)(default_w, zoom_w));
    }

    inline bool ensure_svg_layer_cached(loaded_map& active, const std::string& layer_id,
                                      const map_view_transform& view, const bool allow_zoom_upgrade,
                                      const bool allow_async = false)
    {
        if (!active.svg_doc.valid() || layer_id.empty())
            return false;

        const auto existing = active.layer_rasters.find(layer_id);
        if (existing != active.layer_rasters.end() && existing->second.tex_id != ImTextureID_Invalid)
        {
            if (!allow_zoom_upgrade)
                return true;

            const uint32_t default_w = svg_raster_map::default_raster_width(active.svg_doc);
            const uint32_t zoom_w = static_cast<uint32_t>((std::max)(view.map_w, view.map_h) + 0.5f);
            constexpr uint32_t max_tex_dim = 8192;
            const uint32_t zoom_target = (std::min)(max_tex_dim, (std::max)(default_w, zoom_w));
            constexpr uint32_t upgrade_hysteresis_pct = 120;
            if (zoom_target * 100u <= existing->second.cached_target_w * upgrade_hysteresis_pct)
                return true;
        }

        const uint32_t target_w = svg_target_raster_width(view, active.svg_doc, active.layer_rasters, layer_id);
        return svg_raster_map::ensure_layer_rasterized(active.svg_doc, active.layer_rasters, layer_id, target_w,
            allow_async);
    }

    inline void preload_overlay_tile_layers(loaded_map& active)
    {
        const map_floor* floor = primary_floor(active.config);
        if (!floor)
            return;

        for (const auto& overlay : active.config.overlay_layers)
        {
            if (!overlay_has_tile(overlay))
                continue;

            bool queued = false;
            for (const auto& extent : overlay.extents)
            {
                for (const auto& area : extent.areas)
                {
                    map_bounds bounds{};
                    if (!bounds_from_area(area, bounds))
                        continue;

                    const std::string key = overlay_tile_cache_key(overlay.name, bounds);
                    ensure_tile_layer(active, key, overlay.tile_path, &bounds);
                    queued = true;
                }
            }

            if (queued)
                continue;

            map_bounds bounds{};
            if (!union_overlay_tile_bounds(overlay, bounds))
                bounds = floor->bounds;

            const std::string key = overlay_tile_cache_key(overlay.name, bounds);
            ensure_tile_layer(active, key, overlay.tile_path, &bounds);
        }
    }

    inline void update_active_layers(const float world_x, const float world_y, const float world_z)
    {
        loaded_map& active = active_map();
        if (!active.map_ready)
            return;

        active.active_base_layer = active.config.svg_layer.empty()
            ? std::string("Ground_Level") : active.config.svg_layer;

        active.active_overlay_layer.clear();
        active.active_overlay_name.clear();
        active.active_zone_name.clear();
        active.active_overlay_index = -1;

        active.last_player_x = world_x;
        active.last_player_z = world_z;
        active.has_last_player_pos = true;

        const int overlay_index = pick_overlay_layer_index(active.config, world_x, world_y, world_z);
        if (overlay_index >= 0)
        {
            const map_overlay_layer& overlay = active.config.overlay_layers[static_cast<size_t>(overlay_index)];
            active.active_overlay_index = overlay_index;
            active.active_overlay_layer = overlay.svg_layer;
            active.active_overlay_name = overlay.name;
        }

        active.active_zone_name = pick_active_zone_label(active.config, world_x, world_y, world_z,
            overlay_index);
    }

    inline void ensure_satellite_resources(loaded_map& active)
    {
        if (active.resolved_base_style != map_style::satellite_style || !config_has_tile(active.config))
            return;

        tile_raster_map::cleanup_placeholder_tiles();
        ensure_tile_layer(active, "base", active.config.tile_path);
        preload_overlay_tile_layers(active);
    }

    inline bool ensure_svg_cache_for_map(loaded_map& active)
    {
        if (!config_needs_svg_raster(active.config, active.resolved_base_style))
            return true;

        if (!config_has_svg(active.config))
            return false;

        if (active.resolved_base_style == map_style::satellite_style)
            return true;

        if (!ensure_svg_doc_open(active))
            return false;

        return ensure_svg_base_layer_rasterized(active);
    }

    inline bool apply_active_map_style_switch(loaded_map& active, const map_style requested_style)
    {
        active.active_style = requested_style;
        active.resolved_base_style = resolve_base_style(active.config,
            editor_is_active() ? editor_state().applied_preview_style : requested_style);

        if (!ensure_svg_cache_for_map(active)
            && active.resolved_base_style == map_style::abstract_style)
            return false;

        ensure_satellite_resources(active);
        clear_load_failure();
        return true;
    }

    inline std::string format_map_load_status(const loaded_map& active, const map_style requested_style)
    {
        return "ok:" + active.config.id
            + " req=" + (requested_style == map_style::satellite_style ? "satellite"
                : requested_style == map_style::abstract_style ? "abstract" : "auto")
            + " draw=" + (active.resolved_base_style == map_style::satellite_style ? "satellite" : "abstract")
            + " svg_layers=" + std::to_string(active.layer_rasters.size());
    }

    inline bool load_active_map_now(const std::string& location_id, const bool force_reload = false)
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());

        if (config_count() == 0)
            scan_configs();

        const map_config* config = find_config(location_id);
        if (!config)
        {
            if (!location_id.empty())
                release_active_map();
            last_status() = location_id.empty()
                ? "no location id, configs=" + std::to_string(config_count())
                : "no match for '" + location_id + "', configs=" + std::to_string(config_count());
            return false;
        }

        loaded_map& active = active_map();
        const map_style requested_style = map_load_request_style();
        if (!force_reload && active.map_ready && active.config.id == config->id)
        {
            if (active.active_style == requested_style)
                return true;

            if (apply_active_map_style_switch(active, requested_style))
            {
                last_status() = format_map_load_status(active, requested_style) + " (style_switch)";
                return true;
            }

            failed_load_config_id() = config->id;
            last_status() = "abstract map svg load failed on style switch";
            return false;
        }

        if (failed_load_config_id() == config->id)
            return false;

        release_active_map();
        active.config = *config;
        active.active_style = requested_style;
        active.resolved_base_style = resolve_base_style(active.config,
            editor_is_active() ? editor_state().applied_preview_style : requested_style);
        for (auto& floor : active.config.floors)
            apply_projection_defaults(floor, active.config);

        if (!dxhook::is_ready())
        {
            last_status() = "renderer not ready";
            active.config = {};
            return false;
        }

        const bool svg_loaded = ensure_svg_cache_for_map(active);
        const bool needs_svg = config_needs_svg_raster(active.config, active.resolved_base_style);
        if (needs_svg && !svg_loaded)
        {
            if (active.resolved_base_style == map_style::abstract_style)
            {
                failed_load_config_id() = config->id;
                const std::string svg_name = active.config.svg.empty() ? "map.svg" : active.config.svg;
                const std::string resolved = resolve_map_path(svg_name, config->config_path);
                last_status() = resolved.empty()
                    ? "abstract map needs svg in maps/: " + svg_name
                    : "abstract map svg load failed: " + svg_name;
                active.config = {};
                return false;
            }
            if (!config_has_tile(active.config))
            {
                failed_load_config_id() = config->id;
                last_status() = "put map svg in maps/ (see log for searched paths)";
                active.config = {};
                return false;
            }
        }
        else if (!config_has_tile(active.config) && !config_has_svg(active.config))
        {
            failed_load_config_id() = config->id;
            last_status() = "map config has no svg or tile_path";
            active.config = {};
            return false;
        }

        clear_load_failure();

        active.active_base_layer = active.config.svg_layer.empty()
            ? std::string("Ground_Level") : active.config.svg_layer;
        active.map_ready = true;

        ensure_satellite_resources(active);

        last_status() = format_map_load_status(active, requested_style);
        return true;
    }

    inline void notify_location_changed(const std::string& location_id)
    {
        if (location_id.empty()) return;

        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        clear_load_failure();
        pending_map_location() = location_id;

        const map_config* config = find_config(location_id);
        loaded_map& active = active_map();
        const char* target_id = config ? config->id.c_str() : nullptr;

        if (active.map_ready && (!target_id || active.config.id != target_id))
            release_active_map();

        pending_map_load() = true;
    }

    inline void request_active_map(const std::string& location_id)
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        pending_map_location() = location_id;

        if (config_count() == 0)
        {
            pending_map_load() = true;
            return;
        }

        const map_config* config = find_config(location_id);
        if (!config)
        {
            if (!location_id.empty() && active_map().map_ready)
                release_active_map();
            pending_map_load() = !location_id.empty();
            return;
        }

        loaded_map& active = active_map();
        const map_style requested_style = map_load_request_style();
        if (active.map_ready && active.config.id == config->id && active.active_style == requested_style)
        {
            pending_map_load() = false;
            return;
        }

        if (failed_load_config_id() == config->id)
        {
            pending_map_load() = false;
            return;
        }

        pending_map_load() = true;
    }

    inline void process_pending_map_load()
    {
        if (!pending_map_load())
            return;

        const std::string location = pending_map_location();
        if (load_active_map_now(location))
        {
            pending_map_load() = false;
            return;
        }

        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        const map_config* config = find_config(location);
        if (!config || (config && failed_load_config_id() == config->id))
            pending_map_load() = false;
    }

    inline bool ensure_active_map(const std::string& location_id)
    {
        request_active_map(location_id);

        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        const map_config* config = find_config(location_id);
        if (!config)
            return false;

        const loaded_map& active = active_map();
        return active.map_ready && active.config.id == config->id && active.active_style == get_map_style();
    }

    inline bool should_draw_first_floor(const loaded_map& active)
    {
        return active.active_overlay_layer.empty();
    }

    inline void draw_active_map(ImDrawList* draw_list, const map_view_transform& view, const float opacity_mul = 1.f)
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());

        loaded_map& active = active_map();
        if (!active.map_ready)
            return;

        const map_style draw_style = editor_resolve_draw_style();
        const map_style_calibration& cal = editor_is_active()
            ? editor_calibration_for_style(draw_style)
            : editor_calibration_for_style(active.resolved_base_style);
        world_uv_offset_x() = editor_is_active() ? cal.offset_x : 0.f;
        world_uv_offset_z() = editor_is_active() ? cal.offset_z : 0.f;
        world_uv_rotation_extra_deg() = editor_is_active() ? cal.rotation_deg : 0.f;

        prepare_base_map_texture(active);

        const map_floor* floor = primary_floor(active.config);
        if (!floor)
            return;

        const map_bounds& floor_bounds = floor->bounds;
        const map_style_calibration* editor_abstract_cal = nullptr;
        const map_style_calibration* editor_satellite_cal = nullptr;
        if (editor_is_active())
        {
            editor_abstract_cal = &editor_calibration_for_style(map_style::abstract_style);
            editor_satellite_cal = &editor_calibration_for_style(map_style::satellite_style);
        }
        const int abstract_rotation = resolve_coordinate_rotation(active.config, map_style::abstract_style,
            editor_abstract_cal);
        const int satellite_rotation = resolve_coordinate_rotation(active.config, map_style::satellite_style,
            editor_satellite_cal);
        const bool base_use_svg = active.resolved_base_style == map_style::abstract_style;
        const std::string& first_floor_id = active.config.first_floor_layer.empty()
            ? std::string("First_Floor") : active.config.first_floor_layer;
        const bool overlay_active = active.active_overlay_index >= 0;
        constexpr float base_dim_under_overlay = 0.35f;
        const float base_opacity = overlay_active ? opacity_mul * base_dim_under_overlay : opacity_mul;

        if (base_use_svg)
        {
            ensure_svg_layer_cached(active, active.active_base_layer, view, false);
            if (const svg_raster_map::raster_layer* base = find_layer_raster(active, active.active_base_layer))
                svg_raster_map::draw_layer(draw_list, *base, view, abstract_rotation, base_opacity);
        }
        else if (const auto tile_it = active.tile_rasters.find("base");
                 tile_it != active.tile_rasters.end() && tile_it->second.tex_id != ImTextureID_Invalid)
        {
            tile_raster_map::draw_layer(draw_list, tile_it->second, view, satellite_rotation, base_opacity, floor_bounds);
        }
        else if (ensure_tile_layer(active, "base", active.config.tile_path))
        {
            if (const auto it = active.tile_rasters.find("base"); it != active.tile_rasters.end())
                tile_raster_map::draw_layer(draw_list, it->second, view, satellite_rotation, base_opacity, floor_bounds);
        }

        if (!base_use_svg)
            draw_editor_abstract_overlay(draw_list, active, view, abstract_rotation, floor_bounds, opacity_mul);

        if (base_use_svg && should_draw_first_floor(active))
        {
            ensure_svg_layer_cached(active, first_floor_id, view, false);
            if (const svg_raster_map::raster_layer* first_floor = find_layer_raster(active, first_floor_id))
                svg_raster_map::draw_layer(draw_list, *first_floor, view, abstract_rotation, base_opacity);
        }

        if (active.active_overlay_index >= 0)
        {
            const map_overlay_layer& overlay =
                active.config.overlay_layers[static_cast<size_t>(active.active_overlay_index)];

            const bool use_svg = overlay_use_svg(active.resolved_base_style, overlay);
            if (use_svg)
            {
                if (!overlay.svg_layer.empty()
                    && overlay.svg_layer != active.active_base_layer
                    && overlay.svg_layer != first_floor_id
                    && ensure_svg_doc_open(active))
                {
                    ensure_svg_layer_cached(active, overlay.svg_layer, view, false, true);
                    if (const svg_raster_map::raster_layer* svg_overlay =
                            find_layer_raster(active, overlay.svg_layer))
                        svg_raster_map::draw_layer(draw_list, *svg_overlay, view, abstract_rotation, opacity_mul);
                }
            }
            else
            {
                if (ensure_overlay_tile_layer(active, overlay))
                {
                    map_bounds stitch_bounds{};
                    if (!(active.has_last_player_pos
                            && pick_overlay_tile_bounds(overlay, active.last_player_x, active.last_player_z,
                                stitch_bounds))
                        && !union_overlay_tile_bounds(overlay, stitch_bounds))
                    {
                        stitch_bounds = floor_bounds;
                    }

                    const std::string tile_key = overlay_tile_cache_key(overlay.name, stitch_bounds);
                    if (const auto it = active.tile_rasters.find(tile_key); it != active.tile_rasters.end())
                        tile_raster_map::draw_layer(draw_list, it->second, view, satellite_rotation, opacity_mul, floor_bounds);
                }
            }
        }
    }

    inline std::string format_overlay_floor_label(const std::string& overlay_name)
    {
        if (overlay_name.empty())
            return "Ground floor";

        if (overlay_name.size() >= 5
            && overlay_name.compare(overlay_name.size() - 5, 5, "Floor") == 0)
        {
            std::string formatted = overlay_name;
            formatted[formatted.size() - 5] = 'f';
            return formatted;
        }

        return overlay_name + " floor";
    }

    inline std::string get_minimap_floor_label()
    {
        const loaded_map& active = active_map();

        if (!active.active_zone_name.empty())
        {
            std::string label = active.active_zone_name;
            if (!active.active_overlay_name.empty())
            {
                label += " | ";
                label += format_overlay_floor_label(active.active_overlay_name);
            }
            return label;
        }

        if (!active.active_overlay_name.empty())
            return format_overlay_floor_label(active.active_overlay_name);

        return "Ground";
    }

    inline ImU32 overlay_outline_color(const int layer_index, const float alpha_mul)
    {
        static constexpr ImU32 palette[] = {
            IM_COL32(255, 90, 90, 255),
            IM_COL32(90, 200, 255, 255),
            IM_COL32(255, 200, 80, 255),
            IM_COL32(180, 120, 255, 255),
            IM_COL32(90, 255, 140, 255),
            IM_COL32(255, 140, 220, 255),
        };
        constexpr size_t count = sizeof(palette) / sizeof(palette[0]);
        const ImU32 base = palette[static_cast<size_t>(layer_index) % count];
        const int alpha = static_cast<int>(static_cast<float>((base >> IM_COL32_A_SHIFT) & 0xFF) * alpha_mul);
        return (base & 0x00FFFFFFu) | (static_cast<ImU32>(alpha) << IM_COL32_A_SHIFT);
    }

    inline bool editor_show_zone_outlines()
    {
        const map_editor_state& editor = editor_state();
        return editor.enabled && editor.show_zone_outlines && editor.session_ready;
    }

    inline void draw_overlay_zone_outlines(ImDrawList* draw_list, const map_floor& floor,
                                           const map_view_transform& view,
                                           const std::vector<map_overlay_layer>& layers,
                                           const float opacity_mul, const int highlight_layer)
    {
        if (!draw_list || layers.empty() || opacity_mul <= 0.f)
            return;

        constexpr float thickness = 2.f;
        for (size_t layer_index = 0; layer_index < layers.size(); ++layer_index)
        {
            const map_overlay_layer& layer = layers[layer_index];
            const bool highlighted = static_cast<int>(layer_index) == highlight_layer;
            const float layer_alpha = highlighted ? opacity_mul : opacity_mul * 0.8f;
            const ImU32 color = overlay_outline_color(static_cast<int>(layer_index), layer_alpha);
            const float line_thickness = highlighted ? thickness * 1.5f : thickness;

            for (const auto& extent : layer.extents)
            {
                for (const auto& area : extent.areas)
                {
                    const float corners[4][2] = {
                        {area.min_x, area.min_z},
                        {area.max_x, area.min_z},
                        {area.max_x, area.max_z},
                        {area.min_x, area.max_z},
                    };

                    ImVec2 points[4]{};
                    bool visible = true;
                    for (int i = 0; i < 4; ++i)
                    {
                        if (!world_to_map_point(floor, corners[i][0], corners[i][1], view, points[i]))
                        {
                            visible = false;
                            break;
                        }
                    }
                    if (!visible)
                        continue;

                    draw_list->AddPolyline(points, 4, color, ImDrawFlags_Closed, line_thickness);
                }
            }
        }
    }

    inline const std::string& get_status_message()
    {
        return last_status();
    }

    inline bool get_active_floor(const float world_x, const float world_y, const float world_z,
                                 const map_floor*& out_floor)
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        const loaded_map& active = active_map();
        if (!active.map_ready)
            return false;

        update_active_layers(world_x, world_y, world_z);
        out_floor = pick_floor(active.config, world_y);
        return out_floor != nullptr;
    }

    inline bool get_active_display_bounds(map_bounds& out_bounds)
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        const loaded_map& active = active_map();
        if (!active.map_ready)
            return false;

        const map_floor* floor = primary_floor(active.config);
        if (!floor)
            return false;

        out_bounds = display_bounds_for_active(active, *floor);
        return true;
    }
}