#pragma once

#include "core/asset_helpers/save_ship_config.h"
#include "core/mount_type/mount_type.h"
#include "core/ship_config/ship_config.h"
#include "core/ship_type/ship_type.h"
#include "spdlog/spdlog.h"
#include <RmlUi/Core.h>
#include <RmlUi/Core/Elements/ElementFormControlInput.h>
#include <RmlUi/Debugger.h>
#include <RmlUi_Backend.h>
#include <unordered_map>
#include <vector>

using std::pair;
using std::string;
using std::unordered_map;
using std::vector;

using Rml::DataModelHandle;
using Rml::Event;
using Rml::VariantList;

namespace UI::ShipEditor {

constexpr int editor_image_size = 400;

constexpr int mount_size = 20;

struct ViewModel {
    vector<string> ship_name;
    string ship_image;
    float editor_asset_image_ratio = 0;
    pair<float, float> asset_image_centroid_ratio = {0, 0};
    vector<int> mount_type;
    vector<string> mount_x;
    vector<string> mount_y;
    vector<string> mount_rot_neut;
    vector<string> mount_rot_range;
    vector<string> mount_style;
    int selected_ship_idx = 0;
    int selected_mount_idx = 0;
    string max_speed = "0";
    string max_acc = "0";
    string max_ang_vel = "0";
    string max_ang_acc = "0";
    string scale = "0";
    vector<string> mount_types;
};

class Page {
  public:
    Rml::ElementDocument *document = nullptr;

    vector<ShipConfig> ordered_ship_configs;

    ViewModel vm;

    void initialize(Rml::Context *context, unordered_map<ShipType, ShipConfig> ship_configs) {
        // Init view model
        for (auto &type : ordered_ship_types) {
            ordered_ship_configs.push_back(ship_configs[type]);

            vm.ship_name.push_back(ship_configs[type].display_name);
        }

        vm.mount_types = ordered_mount_type_names;

        select_ship(0);

        // Bind view model
        if (Rml::DataModelConstructor constructor = context->CreateDataModel("ship_editor")) {
            constructor.RegisterArray<vector<string>>();
            constructor.RegisterArray<vector<int>>();

            if (auto sh = constructor.RegisterStruct<ViewModel>()) {
                sh.RegisterMember("mount_type", &ViewModel::mount_type);
                sh.RegisterMember("ship_name", &ViewModel::ship_name);
                sh.RegisterMember("ship_image", &ViewModel::ship_image);
                sh.RegisterMember("mount_x", &ViewModel::mount_x);
                sh.RegisterMember("mount_y", &ViewModel::mount_y);
                sh.RegisterMember("mount_rot_neut", &ViewModel::mount_rot_neut);
                sh.RegisterMember("mount_rot_range", &ViewModel::mount_rot_range);
                sh.RegisterMember("mount_style", &ViewModel::mount_style);
                sh.RegisterMember("selected_ship_idx", &ViewModel::selected_ship_idx);
                sh.RegisterMember("selected_mount_idx", &ViewModel::selected_mount_idx);
                sh.RegisterMember("max_speed", &ViewModel::max_speed);
                sh.RegisterMember("max_acc", &ViewModel::max_acc);
                sh.RegisterMember("max_ang_vel", &ViewModel::max_ang_vel);
                sh.RegisterMember("max_ang_acc", &ViewModel::max_ang_acc);
                sh.RegisterMember("scale", &ViewModel::scale);
                sh.RegisterMember("mount_types", &ViewModel::mount_types);
            }

            constructor.BindEventCallback(
                "on_mount_x_change",
                [this](DataModelHandle handle, Event &event, const VariantList &args) {
                    int idx = args[0].Get<int>();
                    auto value_str = event.GetParameter<Rml::String>("value", "");
                    vm.mount_x[idx] = value_str;
                    try {
                        vm.mount_style[idx] = compute_mount_style(stof(vm.mount_x[idx]), stof(vm.mount_y[idx]));
                    } catch (...) {
                        /* ignore parse failure — dot won't move until input is valid */
                    }
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_mount_y_change",
                [this](DataModelHandle handle, Event &event, const VariantList &args) {
                    int idx = args[0].Get<int>();
                    auto value_str = event.GetParameter<Rml::String>("value", "");
                    vm.mount_y[idx] = value_str;
                    try {
                        vm.mount_style[idx] = compute_mount_style(stof(vm.mount_x[idx]), stof(vm.mount_y[idx]));
                    } catch (...) {
                        /* ignore parse failure — dot won't move until input is valid */
                    }
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_mount_rot_neut_change",
                [this](DataModelHandle handle, Event &event, const VariantList &args) {
                    int idx = args[0].Get<int>();
                    auto value_str = event.GetParameter<Rml::String>("value", "");
                    vm.mount_rot_neut[idx] = value_str;
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_mount_rot_range_change",
                [this](DataModelHandle handle, Event &event, const VariantList &args) {
                    int idx = args[0].Get<int>();
                    auto value_str = event.GetParameter<Rml::String>("value", "");
                    vm.mount_rot_range[idx] = value_str;
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_mount_type_change",
                [this](DataModelHandle handle, Event &event, const VariantList &) {
                    auto value_str = event.GetParameter<Rml::String>("value", "");
                    auto value = stoi(value_str);
                    select_mount_type(value);
                    SPDLOG_INFO("Updated mount type: {}", vm.mount_type[vm.selected_mount_idx]);
                    handle.DirtyVariable("vm");
                    handle.DirtyVariable("get_selected_mount_type_display_value");
                });

            constructor.BindEventCallback(
                "on_max_speed_change",
                [this](DataModelHandle handle, Event &event, const VariantList &) {
                    auto value_str = event.GetParameter<Rml::String>("value", "");
                    vm.max_speed = value_str;
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_max_acc_change",
                [this](DataModelHandle handle, Event &event, const VariantList &) {
                    auto value_str = event.GetParameter<Rml::String>("value", "");
                    vm.max_acc = value_str;
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_max_ang_vel_change",
                [this](DataModelHandle handle, Event &event, const VariantList &) {
                    auto value_str = event.GetParameter<Rml::String>("value", "");
                    vm.max_ang_vel = value_str;
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_max_ang_acc_change",
                [this](DataModelHandle handle, Event &event, const VariantList &) {
                    auto value_str = event.GetParameter<Rml::String>("value", "");
                    vm.max_ang_acc = value_str;
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_scale_change",
                [this](DataModelHandle handle, Event &event, const VariantList &) {
                    auto value_str = event.GetParameter<Rml::String>("value", "");
                    vm.scale = value_str;
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_select_ship",
                [this](DataModelHandle handle, Event &, const VariantList &args) {
                    int idx = args[0].Get<int>();
                    select_ship(idx);
                    handle.DirtyVariable("vm");
                    handle.DirtyVariable("get_selected_mount_type_display_value");
                });

            constructor.BindEventCallback(
                "on_select_mount",
                [this](DataModelHandle handle, Event &, const VariantList &args) {
                    int idx = args[0].Get<int>();
                    vm.selected_mount_idx = idx;
                    handle.DirtyVariable("vm");
                    handle.DirtyVariable("get_selected_mount_type_display_value");
                });

            constructor.BindEventCallback(
                "on_add_mount",
                [this](DataModelHandle handle, Event &, const VariantList &) {
                    add_mount();
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_delete_mount",
                [this](DataModelHandle handle, Event &, const VariantList &) {
                    delete_mount();
                    handle.DirtyVariable("vm");
                });

            constructor.BindEventCallback(
                "on_save_ship",
                [this](DataModelHandle handle, Event &, const VariantList &) {
                    save_ship();
                    handle.DirtyVariable("vm");
                });

            constructor.BindFunc(
                "get_selected_mount_type_display_value",
                [this](Rml::Variant &out) {
                    auto mount_type = static_cast<MountType>(vm.mount_type[vm.selected_mount_idx]);
                    out = to_string(mount_type);
                });

            constructor.Bind("vm", &vm);

            document = context->LoadDocument("build/assets/ui/pages/ship_editor/ship_editor.rml");

            data_model_handle = constructor.GetModelHandle();
        }
    }

    void show() {
        document->Show();
    }

    void hide() {
        document->Hide();
    }

  private:
    Rml::DataModelHandle data_model_handle;

    string compute_mount_style(float mount_x, float mount_y) {
        float draw_x = (editor_image_size * vm.asset_image_centroid_ratio.first) + mount_x * vm.editor_asset_image_ratio - mount_size / 2;
        float draw_y = (editor_image_size * vm.asset_image_centroid_ratio.second) - mount_y * vm.editor_asset_image_ratio - mount_size / 2;
        return fmt::format("left: {}px; top: {}px", draw_x, draw_y);
    }

    void delete_mount() {
        auto idx = vm.selected_mount_idx;

        vm.mount_type[idx] = vm.mount_type[num_mounts() - 1];
        vm.mount_x[idx] = vm.mount_x[num_mounts() - 1];
        vm.mount_y[idx] = vm.mount_y[num_mounts() - 1];
        vm.mount_rot_neut[idx] = vm.mount_rot_neut[num_mounts() - 1];
        vm.mount_rot_range[idx] = vm.mount_rot_range[num_mounts() - 1];
        vm.mount_style[idx] = vm.mount_style[num_mounts() - 1];

        vm.mount_type.pop_back();
        vm.mount_x.pop_back();
        vm.mount_y.pop_back();
        vm.mount_rot_neut.pop_back();
        vm.mount_rot_range.pop_back();
        vm.mount_style.pop_back();

        vm.selected_mount_idx = 0;
    }

    void add_mount() {
        vm.mount_type.push_back(std::to_underlying(MountType::SmallOne));
        vm.mount_x.push_back("0");
        vm.mount_y.push_back("0");
        vm.mount_rot_neut.push_back("0");
        vm.mount_rot_range.push_back("0");
        vm.mount_style.push_back((compute_mount_style(0, 0)));
    }

    void select_ship(int idx) {
        vm.mount_type.clear();
        vm.mount_x.clear();
        vm.mount_y.clear();
        vm.mount_rot_neut.clear();
        vm.mount_rot_range.clear();
        vm.mount_style.clear();

        auto &ship_config = ordered_ship_configs[vm.selected_ship_idx];

        vm.ship_image = "../../../../" + ship_config.image_path;

        vm.editor_asset_image_ratio = editor_image_size / ship_config.image_width;
        vm.asset_image_centroid_ratio = ship_config.centroid_ratio;

        for (auto &mount : ordered_ship_configs[idx].mounts) {
            SPDLOG_INFO("Loading mount type into UI: {}", std::to_underlying(mount.mount_type));

            vm.mount_type.push_back(std::to_underlying(mount.mount_type));
            vm.mount_x.push_back(std::to_string(mount.pos_x_src));
            vm.mount_y.push_back(std::to_string(mount.pos_y_src));
            vm.mount_rot_neut.push_back(std::to_string(mount.rot_neut));
            vm.mount_rot_range.push_back(std::to_string(mount.rot_range));
            vm.mount_style.push_back(compute_mount_style(mount.pos_x_src, mount.pos_y_src));
        }

        vm.selected_ship_idx = idx;
        vm.selected_mount_idx = 0;

        vm.max_acc = std::to_string(ship_config.max_acc);
        vm.max_ang_acc = std::to_string(ship_config.max_ang_acc);
        vm.max_ang_vel = std::to_string(ship_config.max_ang_vel);
        vm.max_speed = std::to_string(ship_config.max_speed);
        vm.scale = std::to_string(ship_config.scale);
    }

    void save_ship() {
        auto &ship_config = ordered_ship_configs[vm.selected_ship_idx];

        ship_config.max_speed = stof(vm.max_speed);
        ship_config.max_ang_vel = stof(vm.max_ang_vel);
        ship_config.max_ang_acc = stof(vm.max_ang_acc);
        ship_config.max_acc = stof(vm.max_acc);
        ship_config.scale = stof(vm.scale);

        ship_config.mounts = {};

        auto mount_idx = 0;
        while (mount_idx < num_mounts()) {
            ship_config.mounts.push_back({
                .pos_x = stof(vm.mount_x[mount_idx]),
                .pos_y = stof(vm.mount_y[mount_idx]),
                .pos_x_src = stof(vm.mount_x[mount_idx]),
                .pos_y_src = stof(vm.mount_y[mount_idx]),
                .rot_neut = stof(vm.mount_rot_neut[mount_idx]),
                .rot_range = stof(vm.mount_rot_range[mount_idx]),
                .mount_type = static_cast<MountType>(vm.mount_type[mount_idx]),
            });

            mount_idx++;
        }

        save_ship_config(ship_config);
    }

    int num_mounts() {
        return vm.mount_x.size();
    }

    void select_mount_type(int idx) {
        vm.mount_type[vm.selected_mount_idx] = std::to_underlying(ordered_mount_types[idx]);
    }
};

} // namespace UI::ShipEditor
