#include "../module/minimap.h"

#include <algorithm>
#include <filesystem>
#include <fstream>

#include <nlohmann/json.hpp>

#include "../module/ModuleManager.h"
#include "../module/item_pricing.h"
#include "../util/logger.h"
#include "../util/tile_raster_map.h"

namespace minimap
{
    namespace
    {
        using json = nlohmann::json;

        void bounds_from_corners(const float x0, const float z0, const float x1, const float z1, map_bounds& out)
        {
            out.min_x = (std::min)(x0, x1);
            out.max_x = (std::max)(x0, x1);
            out.min_z = (std::min)(z0, z1);
            out.max_z = (std::max)(z0, z1);
        }

        void corners_from_bounds(const map_bounds& bounds, map_style_calibration& out)
        {
            out.bounds_corner0_x = bounds.max_x;
            out.bounds_corner0_z = bounds.min_z;
            out.bounds_corner1_x = bounds.min_x;
            out.bounds_corner1_z = bounds.max_z;
        }

        void calibration_from_config(const map_config& config, map_style_calibration& out, const bool satellite)
        {
            const map_floor* floor = primary_floor(config);
            if (!floor)
                return;

            const map_bounds& bounds = satellite || !config.has_svg_bounds
                ? floor->bounds
                : config.svg_bounds;
            corners_from_bounds(bounds, out);
            out.transform[0] = config.transform[0];
            out.transform[1] = config.transform[1];
            out.transform[2] = config.transform[2];
            out.transform[3] = config.transform[3];
            out.rotation_deg = 0.f;
            out.offset_x = 0.f;
            out.offset_z = 0.f;
            out.coordinate_rotation = satellite
                ? config.coordinate_rotation
                : (config.abstract_coordinate_rotation != 0
                    ? config.abstract_coordinate_rotation : config.coordinate_rotation);
        }

        json bounds_corners_json(const map_style_calibration& cal)
        {
            return json::array({
                json::array({cal.bounds_corner0_x, cal.bounds_corner0_z}),
                json::array({cal.bounds_corner1_x, cal.bounds_corner1_z})
            });
        }

        json calibration_json(const map_style_calibration& cal)
        {
            return {
                {"bounds_corners", bounds_corners_json(cal)},
                {"transform", json::array({cal.transform[0], cal.transform[1], cal.transform[2], cal.transform[3]})},
                {"rotation_deg", cal.rotation_deg},
                {"offset_x", cal.offset_x},
                {"offset_z", cal.offset_z},
                {"coordinate_rotation", cal.coordinate_rotation}
            };
        }

        json overlay_layers_json(const std::vector<map_overlay_layer>& layers)
        {
            json arr = json::array();
            for (const auto& layer : layers)
            {
                json node;
                node["name"] = layer.name;
                if (!layer.zone.empty())
                    node["zone"] = layer.zone;
                if (!layer.svg_layer.empty())
                    node["svg_layer"] = layer.svg_layer;
                if (!layer.tile_path.empty())
                    node["tile_path"] = layer.tile_path;

                json extents = json::array();
                for (const auto& extent : layer.extents)
                {
                    json extent_node;
                    extent_node["height_min"] = extent.height_min;
                    extent_node["height_max"] = extent.height_max;
                    json areas = json::array();
                    for (const auto& area : extent.areas)
                    {
                        json area_node = {
                            {"min_x", area.min_x},
                            {"max_x", area.max_x},
                            {"min_z", area.min_z},
                            {"max_z", area.max_z}
                        };
                        if (!area.label.empty())
                            area_node["label"] = area.label;
                        areas.push_back(std::move(area_node));
                    }
                    extent_node["areas"] = std::move(areas);
                    extents.push_back(std::move(extent_node));
                }
                node["extents"] = std::move(extents);
                arr.push_back(std::move(node));
            }
            return arr;
        }

        json serialize_editor_config(const map_config& base, const map_editor_state& editor)
        {
            json root;
            root["id"] = base.id;
            root["aliases"] = base.aliases;
            root["svg"] = base.svg;
            root["svg_layer"] = base.svg_layer;
            root["tile_path"] = base.tile_path;
            root["tile_size"] = base.tile_size;
            root["min_zoom"] = base.min_zoom;
            root["max_zoom"] = base.max_zoom;
            root["projection"] = base.projection;
            root["coordinate_rotation"] = editor.satellite.coordinate_rotation;
            root["abstract_coordinate_rotation"] = editor.abstract.coordinate_rotation;
            root["first_floor_layer"] = base.first_floor_layer;

            root["bounds_corners"] = bounds_corners_json(editor.abstract);
            root["transform"] = json::array({
                editor.satellite.transform[0],
                editor.satellite.transform[1],
                editor.satellite.transform[2],
                editor.satellite.transform[3]
            });

            root["map_calibration"] = {
                {"abstract", calibration_json(editor.abstract)},
                {"satellite", calibration_json(editor.satellite)}
            };

            root["overlay_layers"] = overlay_layers_json(editor.overlay_layers);
            root["note"] = "Saved from in-game map editor. "
                "Abstract mode uses map_calibration.abstract; satellite uses map_calibration.satellite. "
                "Copy values into the live map json as needed.";
            return root;
        }
    }

    map_editor_state& editor_state()
    {
        static map_editor_state state;
        return state;
    }

    void editor_export_exfil_coords()
    {
        ModuleManager::get_instance()->esp_module->export_exfil_coords();
    }

    void editor_set_enabled(const bool enabled)
    {
        map_editor_state& editor = editor_state();
        editor.enabled = enabled;
        if (!enabled)
        {
            editor.tiles_need_rebuild = false;
            editor.changes_applied = false;
        }
        else
            editor_reload_from_active();
    }

    void editor_bind_config(const map_config& config)
    {
        map_editor_state& editor = editor_state();
        if (config.id.empty())
            return;

        if (editor.session_ready && editor.location_id == config.id)
            return;

        editor.location_id = config.id;
        editor.base_coordinate_rotation = config.coordinate_rotation;
        calibration_from_config(config, editor.abstract, false);
        calibration_from_config(config, editor.satellite, true);
        editor.overlay_layers = config.overlay_layers;
        editor.selected_overlay = 0;
        editor.selected_extent = 0;
        editor.session_ready = true;
        editor.tiles_need_rebuild = true;
    }

    void editor_tick()
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        const loaded_map& active = active_map();
        if (!active.map_ready || active.config.id.empty())
            return;

        editor_bind_config(active.config);
    }

    const map_style_calibration& editor_calibration_for_style(const map_style style)
    {
        map_editor_state& editor = editor_state();
        if (editor.changes_applied)
            return style == map_style::satellite_style ? editor.applied_satellite : editor.applied_abstract;
        return style == map_style::satellite_style ? editor.satellite : editor.abstract;
    }

    bool editor_is_active()
    {
        const map_editor_state& editor = editor_state();
        return editor.enabled && editor.session_ready && editor.changes_applied;
    }

    map_style editor_resolve_draw_style()
    {
        const map_editor_state& editor = editor_state();
        if (!editor_is_active())
            return get_map_style();

        return editor.applied_preview_style;
    }

    void editor_apply_to_active(loaded_map& active)
    {
        map_editor_state& editor = editor_state();
        if (!editor.enabled || !editor.session_ready || !editor.changes_applied
            || active.config.id != editor.location_id)
            return;

        const map_style draw_style = editor.applied_preview_style;
        const map_style_calibration& cal = draw_style == map_style::satellite_style
            ? editor.applied_satellite : editor.applied_abstract;
        const map_bounds bounds = bounds_from_calibration(cal);

        for (auto& floor : active.config.floors)
            floor.bounds = bounds;

        active.config.transform[0] = editor.applied_satellite.transform[0];
        active.config.transform[1] = editor.applied_satellite.transform[1];
        active.config.transform[2] = editor.applied_satellite.transform[2];
        active.config.transform[3] = editor.applied_satellite.transform[3];
        active.config.overlay_layers = editor.applied_overlay_layers;
        active.config.coordinate_rotation = editor.applied_satellite.coordinate_rotation;
        active.config.abstract_coordinate_rotation = editor.applied_abstract.coordinate_rotation;

        if (editor.tiles_need_rebuild)
        {
            tile_raster_map::release_layer_cache(active.tile_rasters);
            editor.tiles_need_rebuild = false;
        }

        active.resolved_base_style = resolve_base_style(active.config, draw_style);
    }

    void editor_apply_changes()
    {
        map_editor_state& editor = editor_state();
        if (!editor.enabled || !editor.session_ready)
        {
            logger::warn("Map editor: enable the editor and load a map before applying.");
            return;
        }

        editor.applied_abstract = editor.abstract;
        editor.applied_satellite = editor.satellite;
        editor.applied_overlay_layers = editor.overlay_layers;
        editor.applied_preview_style = editor.preview_style;
        editor.changes_applied = true;
        editor.tiles_need_rebuild = true;

        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        editor_apply_to_active(active_map());
        logger::info("Map editor: changes applied (" + editor.location_id + ")");
    }

    void editor_reload_from_active()
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        const loaded_map& active = active_map();
        if (!active.map_ready)
        {
            logger::warn("Map editor: no active map to reload.");
            return;
        }

        map_editor_state& editor = editor_state();
        editor.session_ready = false;
        editor.changes_applied = false;
        editor.tiles_need_rebuild = false;
        editor_bind_config(active.config);
        logger::info("Map editor: reloaded from active config (" + active.config.id + ")");
    }

    void reload_map_configs_from_disk()
    {
        scan_configs();
        const size_t config_total = config_count();

        std::string location_id;
        {
            std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
            const loaded_map& active = active_map();
            if (active.map_ready && !active.config.id.empty())
                location_id = active.config.id;
            else if (!pending_map_location().empty())
                location_id = pending_map_location();
        }

        if (location_id.empty())
        {
            logger::info("Map configs reloaded from disk (" + std::to_string(config_total) + " configs)");
            return;
        }

        const bool loaded = load_active_map_now(location_id, true);
        if (loaded)
        {
            std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
            pending_map_load() = false;

            map_editor_state& editor = editor_state();
            if (editor.enabled)
            {
                editor.session_ready = false;
                editor.changes_applied = false;
                editor.tiles_need_rebuild = false;
                editor_bind_config(active_map().config);
            }
        }

        if (loaded)
            logger::info("Map configs reloaded from disk (" + std::to_string(config_total)
                + " configs), remapped " + location_id);
        else
            logger::warn("Map configs reloaded from disk (" + std::to_string(config_total)
                + " configs), failed to remap " + location_id);
    }

    bool editor_save_to_edit_folder(std::string& out_path)
    {
        std::lock_guard<std::recursive_mutex> load_lock(map_load_mutex());
        const loaded_map& active = active_map();
        map_editor_state& editor = editor_state();

        if (!active.map_ready || active.config.id.empty())
        {
            logger::warn("Map editor: enter a raid and load a map before saving.");
            return false;
        }

        if (!editor.session_ready)
            editor_bind_config(active.config);

        const std::string maps_dir = get_maps_directory();
        const std::string edit_dir = maps_dir.empty() ? "edit" : item_pricing::join_path(maps_dir, "edit");

        std::error_code ec;
        std::filesystem::create_directories(edit_dir, ec);

        out_path = item_pricing::join_path(edit_dir, active.config.id + ".json");
        const json root = serialize_editor_config(active.config, editor);

        std::ofstream out(out_path);
        if (!out.is_open())
        {
            logger::error("Map editor: failed to write " + out_path);
            return false;
        }

        out << root.dump(2);
        logger::info("Map editor: saved " + out_path);
        return true;
    }

    bool ensure_svg_doc_loaded(loaded_map& active)
    {
        return ensure_svg_doc_open(active);
    }

    bool editor_show_abstract_overlay()
    {
        const map_editor_state& editor = editor_state();
        if (!editor.enabled || !editor.session_ready || !editor.show_abstract_overlay)
            return false;

        const map_style preview_style = editor.changes_applied
            ? editor.applied_preview_style : editor.preview_style;
        if (preview_style != map_style::satellite_style)
            return false;

        const loaded_map& active = active_map();
        return active.map_ready && active.resolved_base_style == map_style::satellite_style;
    }

    void draw_editor_abstract_overlay(ImDrawList* draw_list, loaded_map& active,
                                      const map_view_transform& view, const int abstract_rotation,
                                      const map_bounds& source_bounds, const float opacity_mul)
    {
        if (!editor_show_abstract_overlay() || !draw_list)
            return;

        if (!ensure_svg_doc_loaded(active))
            return;

        const map_style_calibration& abstract_cal =
            editor_calibration_for_style(map_style::abstract_style);
        const float overlay_opacity = editor_state().abstract_overlay_opacity * opacity_mul;
        if (overlay_opacity <= 0.f)
            return;

        ensure_svg_layer_cached(active, active.active_base_layer, view, false);
        const svg_raster_map::raster_layer* base = find_layer_raster(active, active.active_base_layer);
        if (!base || base->tex_id == ImTextureID_Invalid)
            return;

        svg_raster_map::draw_layer_remapped(draw_list, *base, view, abstract_rotation, overlay_opacity,
            source_bounds, abstract_cal);
    }
}