#include "svg_raster_map.h"

#include <algorithm>
#include <atomic>
#include <cmath>
#include <condition_variable>
#include <deque>
#include <fstream>
#include <memory>
#include <mutex>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#if defined(_MSC_VER)
#include <Windows.h>
#endif

#define NANOSVG_IMPLEMENTATION
#include "../external/nanosvg/nanosvg.h"

#define NANOSVGRAST_IMPLEMENTATION
#include "../external/nanosvg/nanosvgrast.h"

#include "../gui/dxhook/dxhook.h"
#include "../module/minimap.h"
#include "logger.h"
#include <imgui.h>

namespace svg_raster_map
{
    namespace
    {
        enum class svg_job_state : int
        {
            queued = 0,
            running = 1,
            ready = 2,
            failed = 3,
        };

        struct svg_raster_job
        {
            std::string layer_name;
            void* doc_impl = nullptr;
            uint32_t clamped_target_w = 0;
            float world_span_m = 0.f;
            float svg_layer_width = 0.f;
            float svg_layer_height = 0.f;
            std::atomic<int> state{static_cast<int>(svg_job_state::queued)};
            std::vector<uint8_t> pixels;
            int raster_w = 0;
            int raster_h = 0;
        };

        std::mutex g_svg_jobs_mutex;
        std::unordered_map<std::string, std::shared_ptr<svg_raster_job>> g_svg_jobs;

        std::mutex g_svg_queue_mutex;
        std::condition_variable g_svg_queue_cv;
        std::deque<std::shared_ptr<svg_raster_job>> g_svg_job_queue;

        std::mutex g_svg_raster_mutex;
        std::atomic<bool> g_svg_worker_started{false};
    }

    struct svg_document_impl
    {
        std::vector<char> file_data;
        NSVGimage* image = nullptr;
        NSVGrasterizer* rast = nullptr;
    };

    static svg_document_impl* doc_impl(const svg_document& doc)
    {
        return static_cast<svg_document_impl*>(doc.impl);
    }

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

    static bool read_file_bytes(const std::string& path, std::vector<char>& out)
    {
#if defined(_MSC_VER)
        std::ifstream input(utf8_to_wide(path), std::ios::binary);
#else
        std::ifstream input(path, std::ios::binary);
#endif
        if (!input.is_open())
            return false;

        input.seekg(0, std::ios::end);
        const std::streamoff size = input.tellg();
        if (size <= 0)
            return false;

        out.resize(static_cast<size_t>(size) + 1u);
        input.seekg(0, std::ios::beg);
        input.read(out.data(), size);
        out[static_cast<size_t>(size)] = '\0';
        return input.good() || input.eof();
    }

    static const char* shape_layer_id(const NSVGshape* shape)
    {
        if (!shape)
            return "";
        if (shape->layerId[0] != '\0')
            return shape->layerId;
        return shape->id;
    }

    static float compute_raster_scale(const NSVGimage* image, const float world_span_m,
                                      const float override_scale, const uint32_t target_raster_width)
    {
        if (override_scale > 0.f)
            return override_scale;

        const float svg_dim = (std::max)(image->width, image->height);
        if (svg_dim <= 0.f)
            return 4.f;

        constexpr float min_scale = 4.f;
        constexpr uint32_t max_tex_dim = 8192;
        constexpr float ref_viewport_px = 400.f;
        constexpr float ref_min_range_m = 25.f;

        float scale = min_scale;
        if (target_raster_width > 0)
        {
            scale = static_cast<float>(target_raster_width) / image->width;
        }
        else if (world_span_m > 1.f)
        {
            scale = (ref_viewport_px * world_span_m) / (2.f * ref_min_range_m * svg_dim);
            scale = (std::max)(min_scale, scale);
        }

        const float tex_w = image->width * scale;
        const float tex_h = image->height * scale;
        if (tex_w > static_cast<float>(max_tex_dim))
            scale = static_cast<float>(max_tex_dim) / image->width;
        if (image->height * scale > static_cast<float>(max_tex_dim))
            scale = (std::min)(scale, static_cast<float>(max_tex_dim) / image->height);

        return (std::max)(min_scale, scale);
    }

    static bool rasterize_visible_layer(NSVGrasterizer* rast, NSVGimage* image, const std::string& layer_name,
                                        const float scale, std::vector<uint8_t>& pixels,
                                        int& out_w, int& out_h)
    {
        struct saved_flags_entry
        {
            NSVGshape* shape = nullptr;
            unsigned char flags = 0;
        };

        std::vector<saved_flags_entry> saved_flags;
        saved_flags.reserve(256);
        bool has_visible_shape = false;

        for (NSVGshape* shape = image->shapes; shape != nullptr; shape = shape->next)
        {
            saved_flags.push_back({shape, shape->flags});
            const bool in_layer = strcmp(shape_layer_id(shape), layer_name.c_str()) == 0;
            if (in_layer)
            {
                shape->flags |= NSVG_FLAGS_VISIBLE;
                has_visible_shape = true;
            }
            else
            {
                shape->flags &= ~static_cast<unsigned char>(NSVG_FLAGS_VISIBLE);
            }
        }

        if (!has_visible_shape)
        {
            for (const auto& entry : saved_flags)
                entry.shape->flags = entry.flags;
            return false;
        }

        out_w = static_cast<int>(image->width * scale + 0.5f);
        out_h = static_cast<int>(image->height * scale + 0.5f);
        if (out_w <= 0 || out_h <= 0)
        {
            for (const auto& entry : saved_flags)
                entry.shape->flags = entry.flags;
            return false;
        }

        pixels.assign(static_cast<size_t>(out_w) * static_cast<size_t>(out_h) * 4u, 0);
        nsvgRasterize(rast, image, 0.f, 0.f, scale, pixels.data(), out_w, out_h, out_w * 4);

        for (const auto& entry : saved_flags)
            entry.shape->flags = entry.flags;
        return true;
    }

    static bool upload_layer_texture(const std::vector<uint8_t>& pixels, const uint32_t width, const uint32_t height,
                                     raster_layer& layer)
    {
        if (!dxhook::is_ready())
        {
            logger::warn("SVG raster: renderer not ready for texture upload");
            return false;
        }

        ImTextureID tex_id = ImTextureID_Invalid;
        ID3D12Resource* resource = nullptr;
        if (!dxhook::create_texture_rgba(pixels.data(), width, height, tex_id, &resource))
            return false;

        layer.tex_id = tex_id;
        layer.dx12_resource = resource;
        layer.raster_width = width;
        layer.raster_height = height;
        return true;
    }

    namespace
    {
        uint32_t clamp_target_width(const svg_document_impl* impl, const uint32_t target_raster_width)
        {
            constexpr uint32_t max_tex_dim = 8192;
            if (!impl || !impl->image)
                return 0;

            return (std::min)(max_tex_dim,
                (std::max)(static_cast<uint32_t>(impl->image->width * 4.f + 0.5f), target_raster_width));
        }

        bool cache_layer_sufficient(const layer_cache& cache, const std::string& layer_name,
                                    const uint32_t clamped_target)
        {
            const auto it = cache.find(layer_name);
            if (it == cache.end() || it->second.tex_id == ImTextureID_Invalid)
                return false;

            if (clamped_target <= it->second.cached_target_w)
                return true;

            constexpr uint32_t upgrade_hysteresis_pct = 120;
            return clamped_target * 100u <= it->second.cached_target_w * upgrade_hysteresis_pct;
        }

        bool rasterize_layer_pixels(svg_document_impl* impl, const svg_document& doc,
                                    const std::string& layer_name, const uint32_t clamped_target,
                                    std::vector<uint8_t>& out_pixels, int& out_w, int& out_h)
        {
            if (!impl || !impl->image || !impl->rast || layer_name.empty() || clamped_target == 0)
                return false;

            const float scale = compute_raster_scale(impl->image, doc.world_span_m, 0.f, clamped_target);
            std::lock_guard<std::mutex> lock(g_svg_raster_mutex);
            return rasterize_visible_layer(impl->rast, impl->image, layer_name, scale, out_pixels, out_w, out_h);
        }

        bool populate_cache_from_pixels(layer_cache& cache, const std::string& layer_name,
                                        const uint32_t clamped_target, const float svg_w, const float svg_h,
                                        const std::vector<uint8_t>& pixels, const int raster_w, const int raster_h)
        {
            raster_layer layer;
            layer.width = svg_w;
            layer.height = svg_h;
            layer.cached_target_w = clamped_target;
            layer.cache_signature = static_cast<uint64_t>(clamped_target);
            if (!upload_layer_texture(pixels, static_cast<uint32_t>(raster_w),
                static_cast<uint32_t>(raster_h), layer))
            {
                logger::warn("SVG raster upload failed for layer: " + layer_name);
                return false;
            }

            cache[layer_name] = std::move(layer);
            return true;
        }

        void cancel_jobs_for_document(void* doc_impl_ptr)
        {
            if (!doc_impl_ptr)
                return;

            std::lock_guard<std::mutex> lock(g_svg_jobs_mutex);
            for (auto it = g_svg_jobs.begin(); it != g_svg_jobs.end();)
            {
                if (it->second->doc_impl == doc_impl_ptr)
                    it = g_svg_jobs.erase(it);
                else
                    ++it;
            }
        }

        void svg_raster_worker_loop()
        {
            for (;;)
            {
                std::shared_ptr<svg_raster_job> job;
                {
                    std::unique_lock<std::mutex> lock(g_svg_queue_mutex);
                    g_svg_queue_cv.wait(lock, []
                    {
                        return !g_svg_job_queue.empty();
                    });

                    job = g_svg_job_queue.front();
                    g_svg_job_queue.pop_front();
                }

                job->state.store(static_cast<int>(svg_job_state::running));

                bool doc_alive = false;
                svg_document_impl* impl = nullptr;
                {
                    std::lock_guard<std::mutex> lock(g_svg_jobs_mutex);
                    const auto it = g_svg_jobs.find(job->layer_name);
                    doc_alive = it != g_svg_jobs.end() && it->second.get() == job.get()
                        && it->second->doc_impl == job->doc_impl;
                }

                if (!doc_alive || !job->doc_impl)
                {
                    job->state.store(static_cast<int>(svg_job_state::failed));
                    continue;
                }

                impl = static_cast<svg_document_impl*>(job->doc_impl);
                svg_document doc{};
                doc.world_span_m = job->world_span_m;
                doc.impl = job->doc_impl;

                std::vector<uint8_t> pixels;
                int raster_w = 0;
                int raster_h = 0;
                if (rasterize_layer_pixels(impl, doc, job->layer_name, job->clamped_target_w,
                    pixels, raster_w, raster_h))
                {
                    job->pixels = std::move(pixels);
                    job->raster_w = raster_w;
                    job->raster_h = raster_h;
                    job->state.store(static_cast<int>(svg_job_state::ready));
                }
                else
                {
                    job->state.store(static_cast<int>(svg_job_state::failed));
                }
            }
        }

        void ensure_svg_worker()
        {
            bool expected = false;
            if (!g_svg_worker_started.compare_exchange_strong(expected, true))
                return;

            std::thread(svg_raster_worker_loop).detach();
        }

        void enqueue_svg_job(const std::shared_ptr<svg_raster_job>& job)
        {
            ensure_svg_worker();
            {
                std::lock_guard<std::mutex> lock(g_svg_queue_mutex);
                g_svg_job_queue.push_back(job);
            }
            g_svg_queue_cv.notify_one();
        }

        bool try_finalize_ready_svg_job(svg_document& doc, layer_cache& cache, const std::string& layer_name,
                                        const uint32_t clamped_target)
        {
            std::shared_ptr<svg_raster_job> job;
            {
                std::lock_guard<std::mutex> lock(g_svg_jobs_mutex);
                const auto it = g_svg_jobs.find(layer_name);
                if (it == g_svg_jobs.end())
                    return false;
                job = it->second;
            }

            if (job->doc_impl != doc.impl || job->clamped_target_w != clamped_target)
                return false;

            if (job->state.load() != static_cast<int>(svg_job_state::ready))
                return false;

            if (!populate_cache_from_pixels(cache, layer_name, clamped_target, job->svg_layer_width,
                job->svg_layer_height, job->pixels, job->raster_w, job->raster_h))
                return false;

            std::lock_guard<std::mutex> lock(g_svg_jobs_mutex);
            g_svg_jobs.erase(layer_name);
            return true;
        }

        void queue_async_svg_raster(svg_document& doc, const std::string& layer_name,
                                    const uint32_t clamped_target, const float svg_w, const float svg_h)
        {
            {
                std::lock_guard<std::mutex> lock(g_svg_jobs_mutex);
                const auto it = g_svg_jobs.find(layer_name);
                if (it != g_svg_jobs.end())
                {
                    const auto& existing = it->second;
                    if (existing->doc_impl == doc.impl && existing->clamped_target_w == clamped_target)
                    {
                        const int state = existing->state.load();
                        if (state == static_cast<int>(svg_job_state::queued)
                            || state == static_cast<int>(svg_job_state::running)
                            || state == static_cast<int>(svg_job_state::ready))
                            return;
                    }
                    g_svg_jobs.erase(it);
                }
            }

            auto job = std::make_shared<svg_raster_job>();
            job->layer_name = layer_name;
            job->doc_impl = doc.impl;
            job->clamped_target_w = clamped_target;
            job->world_span_m = doc.world_span_m;
            job->svg_layer_width = svg_w;
            job->svg_layer_height = svg_h;

            {
                std::lock_guard<std::mutex> lock(g_svg_jobs_mutex);
                g_svg_jobs[layer_name] = job;
            }
            enqueue_svg_job(job);
        }
    }

    void release_layer_cache(layer_cache& layers)
    {
        for (auto& entry : layers)
        {
            if (entry.second.tex_id != ImTextureID_Invalid)
                dxhook::release_texture_id(entry.second.tex_id);
            entry.second = {};
        }
        layers.clear();
    }

    void release_svg_document(svg_document& doc)
    {
        cancel_jobs_for_document(doc.impl);

        if (svg_document_impl* impl = doc_impl(doc))
        {
            if (impl->rast)
                nsvgDeleteRasterizer(impl->rast);
            if (impl->image)
                nsvgDelete(impl->image);
            delete impl;
        }
        doc.impl = nullptr;
        doc.path.clear();
        doc.world_span_m = 0.f;
    }

    bool svg_has_layer(const svg_document& doc, const std::string& layer_name)
    {
        if (layer_name.empty())
            return false;

        const svg_document_impl* impl = doc_impl(doc);
        if (!impl || !impl->image)
            return false;

        for (NSVGshape* shape = impl->image->shapes; shape != nullptr; shape = shape->next)
        {
            if (strcmp(shape_layer_id(shape), layer_name.c_str()) == 0)
                return true;
        }

        return false;
    }

    bool open_svg_document(const std::string& path, const float world_span_m, svg_document& out)
    {
        release_svg_document(out);

        auto impl = std::make_unique<svg_document_impl>();
        if (!read_file_bytes(path, impl->file_data))
        {
            logger::error("SVG read failed: " + path);
            return false;
        }

        impl->image = nsvgParse(impl->file_data.data(), "px", 96.f);
        if (!impl->image || impl->image->width <= 0.f || impl->image->height <= 0.f)
        {
            if (impl->image)
                nsvgDelete(impl->image);
            logger::error("SVG parse failed: " + path);
            return false;
        }

        impl->rast = nsvgCreateRasterizer();
        if (!impl->rast)
        {
            nsvgDelete(impl->image);
            logger::error("SVG rasterizer init failed");
            return false;
        }

        out.path = path;
        out.world_span_m = world_span_m;
        out.impl = impl.release();
        return true;
    }

    uint32_t default_raster_width(const svg_document& doc)
    {
        const svg_document_impl* impl = doc_impl(doc);
        if (!impl || !impl->image)
            return 0;

        const float scale = compute_raster_scale(impl->image, doc.world_span_m, 0.f, 0);
        return static_cast<uint32_t>(impl->image->width * scale + 0.5f);
    }

    float svg_width(const svg_document& doc)
    {
        const svg_document_impl* impl = doc_impl(doc);
        return impl && impl->image ? impl->image->width : 0.f;
    }

    float svg_height(const svg_document& doc)
    {
        const svg_document_impl* impl = doc_impl(doc);
        return impl && impl->image ? impl->image->height : 0.f;
    }

    bool ensure_layer_rasterized(svg_document& doc, layer_cache& cache, const std::string& layer_name,
                                 const uint32_t target_raster_width, const bool allow_async)
    {
        svg_document_impl* impl = doc_impl(doc);
        if (!impl || !impl->image || !impl->rast || layer_name.empty())
            return false;

        const uint32_t clamped_target = clamp_target_width(impl, target_raster_width);
        if (clamped_target == 0)
            return false;

        if (cache_layer_sufficient(cache, layer_name, clamped_target))
            return true;

        const auto it = cache.find(layer_name);
        if (it != cache.end())
        {
            if (it->second.tex_id != ImTextureID_Invalid)
                dxhook::release_texture_id(it->second.tex_id);
            cache.erase(it);
        }

        if (try_finalize_ready_svg_job(doc, cache, layer_name, clamped_target))
            return true;

        if (allow_async)
        {
            queue_async_svg_raster(doc, layer_name, clamped_target, impl->image->width, impl->image->height);
            return false;
        }

        std::vector<uint8_t> pixels;
        int raster_w = 0;
        int raster_h = 0;
        if (!rasterize_layer_pixels(impl, doc, layer_name, clamped_target, pixels, raster_w, raster_h))
            return false;

        return populate_cache_from_pixels(cache, layer_name, clamped_target, impl->image->width,
            impl->image->height, pixels, raster_w, raster_h);
    }

    bool load_layers_from_file(const std::string& path, layer_cache& out,
                               const float world_span_m, const float raster_scale_override)
    {
        svg_document doc;
        if (!open_svg_document(path, world_span_m, doc))
            return false;

        std::unordered_set<std::string> layer_names;
        if (svg_document_impl* impl = doc_impl(doc))
        {
            for (NSVGshape* shape = impl->image->shapes; shape != nullptr; shape = shape->next)
            {
                const char* layer_name = shape_layer_id(shape);
                if (layer_name[0] != '\0')
                    layer_names.insert(layer_name);
            }
        }

        release_layer_cache(out);

        const uint32_t default_width = raster_scale_override > 0.f
            ? static_cast<uint32_t>((svg_width(doc) * raster_scale_override) + 0.5f)
            : default_raster_width(doc);

        size_t uploaded_layers = 0;
        for (const std::string& layer_name : layer_names)
        {
            if (ensure_layer_rasterized(doc, out, layer_name, default_width))
                ++uploaded_layers;
        }

        release_svg_document(doc);

        logger::info("SVG raster layers loaded: " + path + " layers=" + std::to_string(out.size())
            + " default_w=" + std::to_string(default_width));

        return uploaded_layers > 0;
    }

    struct map_vertex
    {
        ImVec2 screen{};
        ImVec2 map_uv{};
    };

    static ImVec2 lerp_vec2(const ImVec2& a, const ImVec2& b, const float t)
    {
        return {a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t};
    }

    static float vertex_axis(const map_vertex& v, const int axis)
    {
        return axis == 0 ? v.map_uv.x : v.map_uv.y;
    }

    static map_vertex lerp_vertex(const map_vertex& a, const map_vertex& b, const float t)
    {
        map_vertex out{};
        out.screen = lerp_vec2(a.screen, b.screen, t);
        out.map_uv = lerp_vec2(a.map_uv, b.map_uv, t);
        return out;
    }

    static std::vector<map_vertex> clip_half_plane(const std::vector<map_vertex>& input,
                                                 const int axis, const float limit, const bool keep_at_or_above)
    {
        std::vector<map_vertex> output;
        if (input.empty())
            return output;

        output.reserve(input.size() + 2);
        const size_t count = input.size();
        for (size_t i = 0; i < count; ++i)
        {
            const map_vertex& current = input[i];
            const map_vertex& next = input[(i + 1) % count];
            const float current_axis = vertex_axis(current, axis);
            const float next_axis = vertex_axis(next, axis);
            const bool current_inside = keep_at_or_above ? current_axis >= limit : current_axis <= limit;
            const bool next_inside = keep_at_or_above ? next_axis >= limit : next_axis <= limit;

            if (current_inside && next_inside)
            {
                output.push_back(next);
            }
            else if (current_inside && !next_inside)
            {
                const float denom = next_axis - current_axis;
                if (fabsf(denom) > 1e-6f)
                {
                    const float t = (limit - current_axis) / denom;
                    output.push_back(lerp_vertex(current, next, t));
                }
            }
            else if (!current_inside && next_inside)
            {
                const float denom = next_axis - current_axis;
                if (fabsf(denom) > 1e-6f)
                {
                    const float t = (limit - current_axis) / denom;
                    output.push_back(lerp_vertex(current, next, t));
                }
                output.push_back(next);
            }
        }
        return output;
    }

    static std::vector<map_vertex> clip_quad_to_map_uv(const map_vertex in[4])
    {
        std::vector<map_vertex> poly(in, in + 4);
        poly = clip_half_plane(poly, 0, 0.f, true);
        if (poly.empty()) return {};
        poly = clip_half_plane(poly, 0, 1.f, false);
        if (poly.empty()) return {};
        poly = clip_half_plane(poly, 1, 0.f, true);
        if (poly.empty()) return {};
        poly = clip_half_plane(poly, 1, 1.f, false);
        return poly;
    }

    static void draw_textured_triangle(ImDrawList* draw_list, const ImTextureID tex_id,
                                       const map_vertex& v0, const map_vertex& v1, const map_vertex& v2,
                                       const int coordinate_rotation, const ImU32 tint)
    {
        const ImVec2 uv0 = minimap::map_tex_uv(coordinate_rotation, v0.map_uv.x, v0.map_uv.y);
        const ImVec2 uv1 = minimap::map_tex_uv(coordinate_rotation, v1.map_uv.x, v1.map_uv.y);
        const ImVec2 uv2 = minimap::map_tex_uv(coordinate_rotation, v2.map_uv.x, v2.map_uv.y);
        draw_list->AddImageQuad(tex_id,
            v0.screen, v1.screen, v2.screen, v2.screen,
            uv0, uv1, uv2, uv2, tint);
    }

    static void draw_clipped_polygon(ImDrawList* draw_list, const ImTextureID tex_id,
                                   const std::vector<map_vertex>& poly, const int coordinate_rotation,
                                   const ImU32 tint)
    {
        if (poly.size() < 3)
            return;

        if (poly.size() == 3)
        {
            draw_textured_triangle(draw_list, tex_id, poly[0], poly[1], poly[2], coordinate_rotation, tint);
            return;
        }

        if (poly.size() == 4)
        {
            const ImVec2 uv0 = minimap::map_tex_uv(coordinate_rotation, poly[0].map_uv.x, poly[0].map_uv.y);
            const ImVec2 uv1 = minimap::map_tex_uv(coordinate_rotation, poly[1].map_uv.x, poly[1].map_uv.y);
            const ImVec2 uv2 = minimap::map_tex_uv(coordinate_rotation, poly[2].map_uv.x, poly[2].map_uv.y);
            const ImVec2 uv3 = minimap::map_tex_uv(coordinate_rotation, poly[3].map_uv.x, poly[3].map_uv.y);
            draw_list->AddImageQuad(tex_id,
                poly[0].screen, poly[1].screen, poly[2].screen, poly[3].screen,
                uv0, uv1, uv2, uv3, tint);
            return;
        }

        for (size_t i = 1; i + 1 < poly.size(); ++i)
            draw_textured_triangle(draw_list, tex_id, poly[0], poly[i], poly[i + 1], coordinate_rotation, tint);
    }

    void draw_layer(ImDrawList* draw_list, const raster_layer& layer, const minimap::map_view_transform& view,
                    const int coordinate_rotation, const float opacity_mul)
    {
        if (!draw_list || layer.tex_id == ImTextureID_Invalid || layer.width <= 0.f || layer.height <= 0.f)
            return;

        const int grid_cells = view.rotate ? 16 : 10;
        const int alpha = static_cast<int>((std::min)(255.f, (std::max)(0.f, 255.f * opacity_mul)));
        const ImU32 tint = IM_COL32(255, 255, 255, alpha);

        for (int gy = 0; gy < grid_cells; ++gy)
        {
            const float sy0 = static_cast<float>(gy) / static_cast<float>(grid_cells);
            const float sy1 = static_cast<float>(gy + 1) / static_cast<float>(grid_cells);

            for (int gx = 0; gx < grid_cells; ++gx)
            {
                const float sx0 = static_cast<float>(gx) / static_cast<float>(grid_cells);
                const float sx1 = static_cast<float>(gx + 1) / static_cast<float>(grid_cells);

                map_vertex corners[4] = {
                    {{view.rx + sx0 * view.size, view.ry + sy0 * view.size}, {}},
                    {{view.rx + sx1 * view.size, view.ry + sy0 * view.size}, {}},
                    {{view.rx + sx1 * view.size, view.ry + sy1 * view.size}, {}},
                    {{view.rx + sx0 * view.size, view.ry + sy1 * view.size}, {}},
                };

                for (int i = 0; i < 4; ++i)
                    corners[i].map_uv = minimap::screen_to_map_uv(view, corners[i].screen);

                const std::vector<map_vertex> clipped = clip_quad_to_map_uv(corners);
                draw_clipped_polygon(draw_list, layer.tex_id, clipped, coordinate_rotation, tint);
            }
        }
    }

    void draw_layer_remapped(ImDrawList* draw_list, const raster_layer& layer,
                             const minimap::map_view_transform& view, const int coordinate_rotation,
                             const float opacity_mul, const minimap::map_bounds& source_bounds,
                             const minimap::map_style_calibration& target_calibration)
    {
        if (!draw_list || layer.tex_id == ImTextureID_Invalid || layer.width <= 0.f || layer.height <= 0.f)
            return;

        const float source_span_x = source_bounds.max_x - source_bounds.min_x;
        const float source_span_z = source_bounds.max_z - source_bounds.min_z;
        if (source_span_x <= 0.f || source_span_z <= 0.f)
            return;

        const int grid_cells = view.rotate ? 16 : 10;
        const int alpha = static_cast<int>((std::min)(255.f, (std::max)(0.f, 255.f * opacity_mul)));
        const ImU32 tint = IM_COL32(255, 255, 255, alpha);

        for (int gy = 0; gy < grid_cells; ++gy)
        {
            const float sy0 = static_cast<float>(gy) / static_cast<float>(grid_cells);
            const float sy1 = static_cast<float>(gy + 1) / static_cast<float>(grid_cells);

            for (int gx = 0; gx < grid_cells; ++gx)
            {
                const float sx0 = static_cast<float>(gx) / static_cast<float>(grid_cells);
                const float sx1 = static_cast<float>(gx + 1) / static_cast<float>(grid_cells);

                map_vertex corners[4] = {
                    {{view.rx + sx0 * view.size, view.ry + sy0 * view.size}, {}},
                    {{view.rx + sx1 * view.size, view.ry + sy0 * view.size}, {}},
                    {{view.rx + sx1 * view.size, view.ry + sy1 * view.size}, {}},
                    {{view.rx + sx0 * view.size, view.ry + sy1 * view.size}, {}},
                };

                for (int i = 0; i < 4; ++i)
                {
                    const ImVec2 source_uv = minimap::screen_to_map_uv(view, corners[i].screen);
                    const float world_x = source_bounds.min_x + source_uv.x * source_span_x;
                    const float world_z = source_bounds.min_z + (1.f - source_uv.y) * source_span_z;
                    float target_u = 0.f;
                    float target_v = 0.f;
                    if (!minimap::world_to_uv_calibrated(target_calibration, world_x, world_z, target_u, target_v))
                    {
                        corners[i].map_uv = source_uv;
                        continue;
                    }
                    corners[i].map_uv = {target_u, target_v};
                }

                const std::vector<map_vertex> clipped = clip_quad_to_map_uv(corners);
                draw_clipped_polygon(draw_list, layer.tex_id, clipped, coordinate_rotation, tint);
            }
        }
    }
}