code:
#include "Core/CommonUI/EventCommandEditor.hpp"
#include "Core/ImGuiExt/ImGuiParsedText.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"

#include "Core/Application.hpp"

#include "Core/Log.hpp"
#include "imgui.h"
#include "imgui_internal.h"

#include <clip.h>
#include <list>

#include <Core/Utils.hpp>
#include <vector>

static clip::format RPGMVEventCommandFormat = -1;

void insertValue(std::string& indentPad, const std::string& val, const std::string& delim) {
  auto pos = indentPad.find(delim);
  if (pos != std::string::npos) {
    indentPad.replace(pos, 2, val);
  }
}
static bool isNestable(const std::shared_ptr<IEventCommand>& selectedCmd) {
  return selectedCmd->code() == EventCode::Conditional_Branch || selectedCmd->code() == EventCode::Else || selectedCmd->code() == EventCode::Loop || selectedCmd->code() == EventCode::If_Win ||
         selectedCmd->code() == EventCode::If_Lose || selectedCmd->code() == EventCode::If_Escape || selectedCmd->code() == EventCode::Show_Choices ||
         selectedCmd->code() == EventCode::When_Selected || selectedCmd->code() == EventCode::When_Cancel;
}

static bool isNestableEnd(const std::shared_ptr<IEventCommand>& selectedCmd) {
  return selectedCmd->code() == EventCode::Repeat_Above || selectedCmd->code() == EventCode::End || selectedCmd->code() == EventCode::End_del_Battle_Processing ||
         selectedCmd->code() == EventCode::End_del_ShowChoices || selectedCmd->code() == EventCode::Else || selectedCmd->code() == EventCode::If_Lose || selectedCmd->code() == EventCode::If_Escape ||
         selectedCmd->code() == EventCode::When_Selected || selectedCmd->code() == EventCode::When_Cancel;
}
void EventCommandEditor::blockSelect(const int n, const bool isDelete = false) {
  if (m_commands->at(n)->hasPartner()) {
    if (!m_commands->at(n)->reverseSelection()) {
      int j = n + 1;
      int partnerCount = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl) || m_commands->at(n)->isCollapsed() ? m_commands->at(n)->partnerCount() : 1;

      if (isDelete) {
        partnerCount = m_commands->at(n)->partnerCount();
      }
      while (partnerCount) {
        if (j >= m_commands->size() || *m_commands->at(j)->indent() < *m_commands->at(n)->indent()) {
          break;
        }

        while (true) {
          if (j >= m_commands->size() || m_commands->at(n)->isPartner(m_commands->at(j)->code(), m_commands->at(j)->indent())) {
            partnerCount--;
            if (m_commands->at(n)->isTerminatingPartner(m_commands->at(j)->code(), m_commands->at(j)->indent())) {
              break;
            }
            if (partnerCount > 0) {
              ++j;
            }
            break;
          }
          ++j;
        }
        if (j >= m_commands->size() - 1 || (m_commands->at(j) && m_commands->at(j)->code() == EventCode::Event_Dummy)) { // ???
          j++;
        }
      }
      m_selectedEnd = j;
    } else {
      m_selectedEnd = n;
      int j = n - 1;
      int partnerCount = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl) ? m_commands->at(n)->partnerCount() : 1;
      while (partnerCount--) {
        if (j < 0 || *m_commands->at(j)->indent() < *m_commands->at(n)->indent()) {
          if (*m_commands->at(j)->indent() < *m_commands->at(n)->indent()) {
            ++j;
          }
          break;
        }
        while (true) {
          if (j < 0 || m_commands->at(n)->isPartner(m_commands->at(j)->code(), m_commands->at(j)->indent())) {
            break;
          }
          --j;
        }
      }
      m_selectedCommand = j;
    }
  } else {
    m_selectedEnd = -1;
  }
}

void EventCommandEditor::handleClipboardInteraction() {
  if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy)) {
    return;
  }

  if (RPGMVEventCommandFormat == -1) {
    RPGMVEventCommandFormat = clip::register_format("application/ore-EventCommand");
  }
  if (ImGui::IsKeyPressed(ImGuiKey_V) && (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl))) {
    clip::lock l;
    if (l.is_convertible(RPGMVEventCommandFormat)) {
      auto len = l.get_data_length(RPGMVEventCommandFormat);
      std::string cmd;
      cmd.resize(len);
      if (l.get_data(RPGMVEventCommandFormat, cmd.data(), len)) {
        nlohmann::ordered_json cmdJson = nlohmann::ordered_json::parse(cmd);
        CommandParser parser;
        auto commands = parser.parse(cmdJson);
        int curIndent = m_selectedCommand > 0 ? *m_commands->at(m_selectedCommand)->indent() : 0;
        int nestedCount = 0;
        if (isNestable(commands[0])) {
          ++nestedCount;
        }

        for (auto& command : commands) {
          if (!command->indent()) {
            command->setIndent(0);
          }
          if (isNestableEnd(command)) {
            if (nestedCount > 0 && curIndent > 0) {
              curIndent--;
            }
            nestedCount--;
          }
          command->setIndent(curIndent);
          if (isNestable(command)) {
            curIndent++;
            nestedCount++;
          }
        }
        m_commands->insert(m_commands->begin() + m_selectedCommand, commands.begin(), commands.end());
      }
    }
  }
  if (ImGui::IsKeyPressed(ImGuiKey_C) && (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl))) {
    clip::lock l;
    int start = m_selectedCommand;
    int end = m_selectedEnd == -1 ? m_selectedCommand + 1 : m_selectedEnd + 1;
    std::vector<std::shared_ptr<IEventCommand>> commands(m_commands->begin() + start, m_commands->begin() + end);
    nlohmann::ordered_json cmdJson;
    CommandParser::serialize(cmdJson, commands);
    auto v = cmdJson.dump();
    l.set_data(RPGMVEventCommandFormat, v.data(), v.size());
  }
  if (ImGui::IsKeyPressed(ImGuiKey_X) && (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl))) {
    if (m_commands->at(m_selectedCommand)->code() != EventCode::Event_Dummy) {
      clip::lock l;
      int start = m_selectedCommand;
      int end = m_selectedEnd == -1 ? m_selectedCommand + 1 : m_selectedEnd + 1;
      std::vector<std::shared_ptr<IEventCommand>> commands(m_commands->begin() + start, m_commands->begin() + end);
      nlohmann::ordered_json cmdJson;
      CommandParser::serialize(cmdJson, commands);
      auto v = cmdJson.dump();
      l.set_data(RPGMVEventCommandFormat, v.data(), v.size());
      if (m_commands->at(m_selectedCommand)->isParent()) {
        blockSelect(m_selectedCommand);
        m_commands->erase(m_commands->begin() + m_selectedCommand, m_commands->begin() + m_selectedEnd + 1);
        m_selectedEnd = -1;
      } else {
        m_commands->erase(m_commands->begin() + start, m_commands->begin() + end);
      }
    }
  }
}
void EventCommandEditor::setupTableHeader() {
  ImGui::TableSetupColumn("Step##command_selectable_column", ImGuiTableColumnFlags_WidthFixed);
  ImGui::TableSetupColumn("##collapse", ImGuiTableColumnFlags_WidthFixed);
  ImGui::TableSetupColumn("Command Operation", ImGuiTableColumnFlags_WidthFixed);
  ImGui::TableSetupScrollFreeze(2, 1);
  ImGui::TableHeadersRow();
}

void EventCommandEditor::setupTableColors() {
  // ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.37f, 0.37f, 0.37f, 0.43f));
  // ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.37f, 0.37f, 0.37f, 0.78f));
  // ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.37f, 0.37f, 0.37f, 0.68f));
  // ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImVec4(0.03f, 0.21f, 0.26f, 1.00f));
  // ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImVec4(0.04f, 0.16f, 0.19f, 1.00f));
}
void EventCommandEditor::handleBlockCollapse(int& n) const {
  if (m_commands->at(n)->collapsable()) {
    const int oldN = n;
    if (const auto& cmd = m_commands->at(n); cmd->isCollapsed()) {
      std::vector<std::string> tooltip;
      auto next = m_commands->at(n + 1);
      const int indent = next->indent().value_or(0);
      bool addedElipses = false;
      while (!cmd->isPartner(next->code(), next->indent())) {
        if ((n + 1) >= m_commands->size() - 1) {
          break;
        }
        n++;

        if (next->code() != EventCode::Event_Dummy) {
          if (tooltip.size() < 5) {
            auto splitRep = splitString(next->stringRep(*Database::instance(), true), '\n');
            for (std::string s : splitRep) {
              if (tooltip.size() >= 5) {
                break;
              }
              tooltip.push_back(s.erase(0, indent * 2));
            }
          } else if (!addedElipses) {
            auto tmp = *next->indent() - indent;
            // If we're at the same indent we don't want to drop down a level
            if (tmp <= 0) {
              tmp = indent;
            }
            tooltip.push_back(next->indentText(tmp) + "\u2026");
            addedElipses = true;
          }
        }
        next = m_commands->at(n + 1);
      }
      ImGui::SetNextWindowSizeConstraints({}, ImGui::GetDPIScaledSize(640, 640));
      if (m_hoveringCommand == oldN && !tooltip.empty() && ImGui::BeginTooltipEx(ImGuiTooltipFlags_None, ImGuiWindowFlags_NoScrollbar)) {
        ImGui::TextParsed("%s", std::accumulate(std::next(tooltip.begin()), tooltip.end(), tooltip[0], [](const std::string& a, const std::string& b) { return a + "\n" + b; }).c_str());
        ImGui::EndTooltip();
      }
    }
  }
}
void EventCommandEditor::draw() {
  if (!ImGui::IsWindowHovered()) {
    // m_hoveringCommand = -1; -- Not working. This causes the PopupContextMenu to deselect and cause issues.
  }

  ImGui::BeginChild("##event_command_editor");
  {
    ImGui::Text("Content:");
    ImGui::PushFont(App::APP->getMonoFont(), Settings::instance()->monoFontSize);
    if (ImGui::BeginTable("##commonevent_code_contents", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, ImGui::GetContentRegionAvail())) {
      setupTableColors();
      setupTableHeader();
      const int totalPadding = std::max(static_cast<int>(std::floor(std::log10(m_commands->size()))), 3);
      if (m_commands) {
        float maxWidth = 0.f;
        // Unfortunately ImGui's table doesn't take into account all items, only the visible ones, we want to take into account all commands so the table doesn't jitter around as the user scrolls
        // vertically, this is a bit expensive, but doesn't seem to greatly impact performance, if there is a better way in the future this should be removed/replaced
        for (const auto& cmd : *m_commands) {
          if (!cmd) {
            continue;
          }
          maxWidth = std::max(maxWidth, ImGui::CalcItemSize(ImGui::CalcTextSize(cmd->stringRep(*Database::instance(), false).c_str()), 0.f, 0.f).x);
        }

        for (int n = 0; n < m_commands->size(); n++) {
          if (!m_commands->at(n)) {
            continue;
          }
          switch (const auto& cmd = m_commands->at(n); cmd->code()) {
          case EventCode::Enemy_Recover_All:
            std::dynamic_pointer_cast<EnemyRecoverAllCommand>(cmd)->m_troopId = m_troopId;
            break;
          case EventCode::Enemy_Appear:
            std::dynamic_pointer_cast<EnemyAppearCommand>(cmd)->m_troopId = m_troopId;
            break;
          case EventCode::Enemy_Transform:
            std::dynamic_pointer_cast<EnemyTransformCommand>(cmd)->m_troopId = m_troopId;
            break;
          case EventCode::Change_Enemy_HP:
            std::dynamic_pointer_cast<ChangeEnemyHPCommand>(cmd)->m_troopId = m_troopId;
            break;
          case EventCode::Change_Enemy_MP:
            std::dynamic_pointer_cast<ChangeEnemyMPCommand>(cmd)->m_troopId = m_troopId;
            break;
          case EventCode::Change_Enemy_TP:
            std::dynamic_pointer_cast<ChangeEnemyTPCommand>(cmd)->m_troopId = m_troopId;
            break;
          case EventCode::Change_Enemy_State:
            std::dynamic_pointer_cast<ChangeEnemyStateCommand>(cmd)->m_troopId = m_troopId;
            break;
          case EventCode::Conditional_Branch:
            std::dynamic_pointer_cast<ConditionalBranchCommand>(cmd)->m_troopId = m_troopId;
            break;
          case EventCode::Control_Variables:
            std::dynamic_pointer_cast<ControlVariables>(cmd)->m_troopId = m_troopId;
            break;
          case EventCode::Show_Battle_Animation:
            std::dynamic_pointer_cast<ShowBattleAnimationCommand>(cmd)->m_troopId = m_troopId;
          default:
            break;
          }
          const bool isSelected = (m_selectedCommand == n || (m_selectedEnd != -1 && n > m_selectedCommand && n <= m_selectedEnd));
          std::string indentPad = m_commands->at(n)->stringRep(*Database::instance());
          auto str = splitString(indentPad, '\n');
          float height = 0.f;
          for (int i = 0; i < str.size(); ++i) {
            height += ImGui::CalcTextSize(str[i].c_str()).y;
            if (i < str.size() - 1) {
              height += ImGui::GetStyle().ItemSpacing.y;
            }
          }

          ImGui::TableNextRow();
          if (ImGui::TableNextColumn()) {
            const int step = n + 1;
            const int stepPadding = (totalPadding - static_cast<int>(std::floor(std::log10(step))));
            if (ImGui::SelectableWithBorder((std::string(stepPadding, ' ') + std::to_string(step)).c_str(), isSelected,
                                            ImGuiSelectableFlags_AllowOverlap | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick | ImGuiSelectableFlags_AllowOverlap,
                                            ImVec2(0, height))) {

              if (ImGui::IsKeyDown(ImGuiKey_LeftAlt) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) {
                /* If a command has a partner try to flip the selection around, no-op for commands that don't support this */
                if (m_commands->at(n)->hasPartner()) {
                  m_commands->at(n)->setReverseSelection(!m_commands->at(n)->reverseSelection());
                }
              } else if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                m_isRequested = true;
                if (m_commands->at(n)->code() == EventCode::Event_Dummy) {
                  m_isNewEntry = true;
                  ImGui::OpenPopup("###command_picker");
                } else {
                  m_isNewEntry = false;
                  /* These are generated automatically based on their related event command dialogs */
                  if (m_commands->at(n)->code() != EventCode::Repeat_Above && m_commands->at(n)->code() != EventCode::When_Selected && m_commands->at(n)->code() != EventCode::When_Cancel &&
                      m_commands->at(n)->code() != EventCode::Else && m_commands->at(n)->code() != EventCode::End) {
                    m_commandDialog = CreateCommandDialog(m_commands->at(n)->code(), m_commands->at(n));
                    if (m_commands->at(n)->code() == EventCode::Conditional_Branch) {
                      blockSelect(n, true);
                      for (int i = n; i < m_selectedEnd; i++) {
                        if (m_commands->at(n)->isPartner(m_commands->at(i)->code(), m_commands->at(n)->indent())) {
                        }
                      }
                    }
                    m_commandDialog->setOpen(true);
                    // ImGui::OpenPopup("###command_picker");
                  }
                }
              }
              if (!ImGui::IsKeyDown(ImGuiKey_LeftShift) && !ImGui::IsKeyDown(ImGuiKey_RightShift)) {
                m_selectedCommand = n;
              }

              if (ImGui::IsKeyDown(ImGuiKey_LeftAlt) || ImGui::IsKeyDown(ImGuiKey_RightAlt) || ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) {
                blockSelect(n);
              } else if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
                if (m_selectedCommand == -1) {
                  m_selectedCommand = n;
                }

                if (n < m_selectedEnd) {
                  m_selectedCommand = n;
                } else {
                  m_selectedEnd = n;
                }
              } else {
                m_selectedEnd = -1;
              }
              if (m_commands->at(n)->code() == EventCode::Show_Choices) {
                blockSelect(n);
              }
            }
            if (ImGui::IsItemHovered()) {
              m_hoveringCommand = n;
            }
          }
          if (ImGui::TableNextColumn()) {
            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
            auto size = ImGui::CalcTextSize("+");
            size.x = size.y;
            size = ImGui::CalcItemSize(size, 0, 0);
            if (const auto cmd = m_commands->at(n); cmd->collapsable()) {
              if (ImGui::Button(std::format("{}##orpg_command_collapse_btn_{}", cmd->isCollapsed() ? "+" : "-", n).c_str(), size)) {
                cmd->setCollapsed(!cmd->isCollapsed());
              }
            } else {
              /* Use a dummy to keep consistent sizing */
              ImGui::Dummy(size);
            }
            ImGui::PopStyleVar();
          }
          if (ImGui::TableNextColumn()) {
            for (const auto& s : str) {
              // Hack to keep scrollbar sizing consistent
              const auto oldPos = ImGui::GetCursorPos();
              ImGui::Dummy({maxWidth, 1.f});
              ImGui::SetCursorPos(oldPos);
              ImGui::TextParsed("%s", s.c_str());
            }
          }

          if (isSelected)
            ImGui::SetItemDefaultFocus();
          handleBlockCollapse(n);
        }
      }
      ImGui::PopFont();
      // ImGui::PopStyleColor(5);

      if (ImGui::BeginPopupContextWindow()) {
        if (m_selectedCommand != m_hoveringCommand) {
          m_selectedCommand = m_hoveringCommand;
        }
        if (ImGui::MenuItem(trNOOP("New..."))) {
          m_isRequested = true;
          m_isNewEntry = true;
        }
        ImGui::EndPopup();
      }
      if (m_isNewEntry)
        ImGui::OpenPopup("###command_picker");

      drawPopup();

      // We don't want to draw this if the command window is open, or they'll conflict with each other
      if (!ImGui::IsPopupOpen("###command_picker")) {
        drawCommandDialog();
      }

      ImGui::EndTable();
    } else {
      ImGui::PopFont();
    }
    if (ImGui::IsKeyPressed((ImGuiKey_Delete)) && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy)) {
      if (m_commands->at(m_selectedCommand)->isParent()) {
        blockSelect(m_selectedCommand, true);
        m_commands->erase(m_commands->begin() + m_selectedCommand, m_commands->begin() + m_selectedEnd + 1);
        m_selectedEnd = -1;
      } else {
        if (m_commands->at(m_selectedCommand)->code() != EventCode::Event_Dummy) {
          int start = m_selectedCommand;
          int end = m_selectedEnd == -1 ? m_selectedCommand + 1 : m_selectedEnd + 1;
          m_commands->erase(m_commands->begin() + start, m_commands->begin() + end);
          m_selectedEnd = -1;
        }
      }
    }
  }
  ImGui::EndChild();
  handleClipboardInteraction();
}

void EventCommandEditor::drawCommandDialog() {
  if (m_commandDialog) {
    auto [closed, confirmed] = m_commandDialog->draw();
    if (!m_commandDialog->getParentIndent().has_value()) {
      m_commandDialog->setParentIndent(m_commands->at(m_selectedCommand)->indent().value());
      m_commandDialog->getCommand()->setIndent(m_commandDialog->getParentIndent().value());
    }
    if (closed) {
      if (confirmed) {
        if (m_isNewEntry) {
          std::vector<std::shared_ptr<IEventCommand>> cmds = m_commandDialog->getBatchCommands();
          if (cmds.empty()) {
            auto select = m_commands->insert(m_commands->begin() + m_selectedCommand, m_commandDialog->getCommand());
            m_selectedCommand = select - m_commands->begin();
            m_selectedEnd = -1;
          } else {
            for (auto cmd : cmds) {
              auto selection = m_commands->insert(m_commands->begin() + m_selectedCommand, cmd);
              m_selectedCommand = (selection + 1) - m_commands->begin();
            }
          }
          m_isNewEntry = false;
        } else {
          if (m_commandDialog->getCommand()->code() == EventCode::Show_Choices) {
            std::shared_ptr<ShowChoiceCommand> pointerCmd = std::dynamic_pointer_cast<ShowChoiceCommand>(m_commandDialog->getCommand());
            // std::shared_ptr<ShowChoiceCommand> selectedCmd = std::dynamic_pointer_cast<ShowChoiceCommand>(m_commands->at(m_selectedCommand));

            int choicesTotal{0};
            int totalChoices{0};
            int m_choiceEnd{0};
            int m_choiceStart{0};
            std::vector<std::shared_ptr<IEventCommand>> whenList;
            std::vector<std::shared_ptr<IEventCommand>> cmdList;
            std::map<int, std::vector<std::shared_ptr<IEventCommand>>> tmp;

            // std::vector<std::string> listChoices;
            std::vector<std::string> finalChoices;

            for (int i = m_selectedCommand + 1; i < m_commands->size(); i++) {
              if (m_commands->at(i)->code() == EventCode::When_Selected && m_commands->at(i)->indent() == pointerCmd->indent().value()) {
                choicesTotal++;
              } else if (m_commands->at(i)->code() == EventCode::When_Cancel && m_commands->at(i)->indent() == pointerCmd->indent().value()) {
                choicesTotal++;
              }
            }

            int choiceDifference = pointerCmd->choices.size() - choicesTotal;
            choiceDifference = abs(choiceDifference);

            bool cancelFound{false};
            bool cancelCreate{false};
            bool cancelDelete{false};
            bool choiceEnd{false};
            if (choiceDifference == 0) {
              // just update the names
              int updateIndex{0};
              for (int i = m_selectedCommand + 1; i < m_commands->size(); i++) {
                if (!choiceEnd) {
                  if (m_commands->at(i)->code() == EventCode::End_del_ShowChoices && m_commands->at(i)->indent() == pointerCmd->indent().value()) {
                    choiceEnd = true;
                  }
                  if (m_commands->at(i)->code() == EventCode::When_Selected && m_commands->at(i)->indent() == pointerCmd->indent().value()) {
                    std::shared_ptr<WhenSelectedCommand> cmd = std::dynamic_pointer_cast<WhenSelectedCommand>(m_commands->at(i));
                    cmd->choice = pointerCmd->choices.at(updateIndex);
                    updateIndex++;
                  }
                  if (m_commands->at(i)->code() == EventCode::When_Cancel && m_commands->at(i)->indent() == pointerCmd->indent().value()) {
                    cancelFound = true;
                  }
                  if (pointerCmd->cancelType == -2) {
                    if (cancelFound) {
                      // Nothing
                    } else {
                      // No cancel and cancelType is set. We need to make a cancel
                      cancelCreate = true;
                    }
                  } else {
                    if (cancelFound) {
                      // Needs to be removed
                      cancelDelete = true;
                    } else {
                      // Nothing
                    }
                  }
                  m_choiceEnd = i;
                }
              }
              if (cancelCreate) {
                std::shared_ptr<EventDummy> dummy = std::make_shared<EventDummy>();
                dummy->setIndent(pointerCmd->indent().value() + 1);
                m_commands->insert(m_commands->begin() + m_choiceEnd, dummy);

                std::shared_ptr<WhenCancelCommand> whenCmd = std::make_shared<WhenCancelCommand>();
                whenCmd->setIndent(m_commandDialog->getParentIndent().value());
                m_commands->insert(m_commands->begin() + m_choiceEnd, whenCmd);
              } else if (cancelDelete) {
                m_commands->erase(m_commands->begin() + m_choiceStart, m_commands->begin() + m_choiceEnd - 1);
              }
            } else {
              for (int i = m_selectedCommand + 1; i < m_commands->size(); i++) {
                if (!choiceEnd) {
                  if (m_commands->at(i)->code() == EventCode::End_del_ShowChoices && m_commands->at(i)->indent() == pointerCmd->indent().value()) {
                    choiceEnd = true;
                  }
                  if (m_commands->at(i)->code() == EventCode::When_Selected && m_commands->at(i)->indent() == pointerCmd->indent().value()) {
                    std::shared_ptr<WhenSelectedCommand> cmd = std::dynamic_pointer_cast<WhenSelectedCommand>(m_commands->at(i));
                    if (!whenList.empty()) {
                      tmp.insert(std::make_pair(totalChoices, whenList)); // Adds any existing when commands into tmp as index
                      whenList.clear();
                      totalChoices++;
                    }
                    // listChoices.push_back(cmd->choice);
                  } else if (m_commands->at(i)->code() == EventCode::When_Cancel && m_commands->at(i)->indent() == pointerCmd->indent().value()) {
                    if (!whenList.empty()) {
                      tmp.insert(std::make_pair(totalChoices, whenList)); // Adds any existing when commands into tmp as index
                      whenList.clear();
                      totalChoices++;
                    }
                    // listChoices.push_back("\\CANCEL");
                  } else {
                    if (!choiceEnd) {
                      whenList.push_back(m_commands->at(i));
                    }
                  }
                  m_choiceEnd = i;
                }
              }
              if (!whenList.empty()) {
                tmp.insert(std::make_pair(totalChoices, whenList)); // Adds any existing when commands into tmp as index
                whenList.clear();
              }

              // Construct the final show choice branches
              cmdList.push_back(pointerCmd);
              int tempIndex{0};
              for (auto& str : pointerCmd->choices) {
                std::shared_ptr<WhenSelectedCommand> whenCmd = std::make_shared<WhenSelectedCommand>();
                whenCmd->setIndent(m_commandDialog->getParentIndent().value());
                whenCmd->choice = str;
                cmdList.insert(cmdList.end(), whenCmd);
                if (tempIndex < tmp.size()) {
                  for (auto& listCmd : tmp[tempIndex]) {
                    cmdList.insert(cmdList.end(), listCmd);
                  }
                } else {
                  // Insert event dummy, no commands
                  std::shared_ptr<EventDummy> dummy = std::make_shared<EventDummy>();
                  dummy->setIndent(pointerCmd->indent().value() + 1);
                  cmdList.insert(cmdList.end(), dummy);
                }
                tempIndex++;
              }
              if (pointerCmd->cancelType == -2) {
                std::shared_ptr<WhenCancelCommand> endCmd = std::make_shared<WhenCancelCommand>();
                endCmd->setIndent(m_commandDialog->getParentIndent().value());
                cmdList.push_back(endCmd);
                if (tempIndex < tmp.size()) {
                  for (auto& listCmd : tmp[tempIndex]) {
                    cmdList.insert(cmdList.end(), listCmd);
                  }
                } else {
                  // Insert event dummy, no commands
                  std::shared_ptr<EventDummy> dummy = std::make_shared<EventDummy>();
                  dummy->setIndent(pointerCmd->indent().value() + 1);
                  cmdList.insert(cmdList.end(), dummy);
                }
              }

              std::shared_ptr<ShowChoicesEndCommand> endCmd = std::make_shared<ShowChoicesEndCommand>();
              endCmd->setIndent(m_commandDialog->getParentIndent().value());
              cmdList.push_back(endCmd);

              m_commands->erase(m_commands->begin() + m_selectedCommand, m_commands->begin() + m_choiceEnd + 1);
              m_commands->insert(m_commands->begin() + m_selectedCommand, cmdList.begin(), cmdList.end());
            }
          }
          if (m_commandDialog->getCommand()->code() == EventCode::Conditional_Branch) {
            if (!m_commandDialog->isCurrentElseBranch()) {
              std::vector<std::shared_ptr<IEventCommand>> tempCmds;

              for (int i = m_selectedCommand + 1; i < m_commands->size(); i++) {
                if (m_commands->at(m_selectedCommand)->isTerminatingPartner(m_commands->at(i)->code(), m_commands->at(i)->indent())) {
                  break;
                }
                if (m_commands->at(i)->code() == EventCode::Else) {
                  break;
                }
                tempCmds.push_back(m_commands->at(i));
              }
              std::vector<std::shared_ptr<IEventCommand>> commandCmds = m_commandDialog->getBatchCommands(tempCmds);
              blockSelect(m_selectedCommand, true);
              m_commands->erase(m_commands->begin() + m_selectedCommand, m_commands->begin() + m_selectedEnd + 1);
              m_commands->insert(m_commands->begin() + m_selectedCommand, commandCmds.begin(), commandCmds.end());
              // Delete branch, store contents, redraw and insert
              m_commandDialog->setCurrentElseBranch();
              m_selectedEnd = -1;
            }
          }
        }

        if (m_commands->at(m_selectedCommand)->code() == EventCode::Set_Movement_Route) {
          std::shared_ptr<SetMovementRouteCommand> routeCmd = std::dynamic_pointer_cast<SetMovementRouteCommand>(m_commands->at(m_selectedCommand));

          routeCmd->editNodes.clear();
          for (const auto& cmd : routeCmd->route.list()) {
            if (cmd->code() != EventCode::Event_Dummy) {
              routeCmd->editNodes.emplace_back(std::make_shared<MovementRouteStepCommand>(m_commands->at(m_selectedCommand)->indent()));
              routeCmd->editNodes.back()->step = cmd;
            }
          }
        }
        if (m_commands->at(m_selectedCommand)->code() == EventCode::Show_Text) {
          std::shared_ptr<ShowTextCommand> showCmd = std::dynamic_pointer_cast<ShowTextCommand>(m_commands->at(m_selectedCommand));

          auto split = splitString(showCmd->textLine, '\n');
          while (split.size() > 1 && split[split.size() - 1].empty()) {
            split.pop_back();
          }
          int index{0};
          int textIndex{0};
          for (const auto& str : split) {
            if (split.size() > 4) {
              if (textIndex == 0) {

                // auto selection = m_commands->insert(m_commands->begin() + m_selectedCommand, cmd);
                // m_selectedCommand = (selection + 1) - m_commands->begin();

                // m_moreCommands.push_back(m_command);
              }
              /*
              auto selection = m_commands->insert(m_commands->begin() + m_selectedCommand, std::make_shared<NextTextCommand>());
              m_selectedCommand = (selection + 1) - m_commands->begin();

              m_commands->at(m_selectedCommand).
              showCmd.back()->text.push_back(std::make_shared<NextTextCommand>());
              showCmd.back()->text.back()->indent() = m_command->indent().value();
              m_moreCommands.back()->text.back()->text = str;
              textIndex++;
              if (m_moreCommands.back()->text.size() >= 4 && textIndex < split.size()) {
                m_moreCommands.push_back(std::make_shared<ShowTextCommand>());
                m_moreCommands.back()->faceImage = m_faceImage;
                m_moreCommands.back()->faceIndex = m_faceIndex;
                m_moreCommands.back()->background = m_background;
                m_moreCommands.back()->position = m_position;
              }

              for (auto cmd : cmds) {
                auto selection = m_commands->insert(m_commands->begin() + m_selectedCommand, cmd);
                m_selectedCommand = (selection + 1) - m_commands->begin();
              }
              */
            } else {
              if (index == 0) {
                showCmd->text.clear();
              }
              showCmd->text.emplace_back(std::make_shared<NextTextCommand>(m_commands->at(m_selectedCommand)->indent()));
              showCmd->text.back()->text = str;
            }
            index++;
          }
        }
      }
      m_commandDialog.reset();
      ImGui::CloseCurrentPopup();
    }
  }
}

void EventCommandEditor::drawCommandTab(const EventCommandTab& tab, const std::string& id, ImVec2 size) {
  if (ImGui::BeginTabItem(std::format("{}###{}", tab.title, id).c_str())) {
    ImGui::BeginHorizontal(std::format("commandDialog_{}_horizontal", tab.title).c_str(), {-1, -1});
    {
      for (int i = 0; const auto& column : tab.buttonColumns) {
        ImGui::BeginVertical(std::format("commandDialog_{}_{}", tab.title, i).c_str(), {-1, -1});
        {
          for (const auto& [code, needsElipses, override, forMV, forMZ] : column) {
            const auto label = (override.empty() ? DecodeEnumName(code) : override) + (needsElipses ? "..." : "");
            if ((forMV && Database::instance()->isMV()) || (forMZ && Database::instance()->isMZ())) {
              if (ImGui::Button(label.c_str(), size)) {
                m_commandDialog = CreateCommandDialog(code);
                if (m_commandDialog) {
                  m_commandDialog->setOpen(true);
                }
              }
            }
          }
        }
        ImGui::EndVertical();
        ++i;
      }
    }
    ImGui::EndHorizontal();
    ImGui::EndTabItem();
  }
}

std::list<EventCommandEditor::EventCommandTab> EventCommandEditor::buildTabList() {
  return {
      EventCommandTab{
          tr("Actor"),
          {
              {
                  {EventCode::Change_HP, true},
                  {EventCode::Change_MP, true},
                  {EventCode::Change_TP, true},
                  {EventCode::Change_EXP, true},
                  {EventCode::Change_Level, true},
                  {EventCode::Change_Skill, true},
              },
              {
                  {EventCode::Change_Gold, true},
                  {EventCode::Change_State, true},
                  {EventCode::Change_Equipment, true},
                  {EventCode::Change_Name, true},
                  {EventCode::Change_Class, true},
                  {EventCode::Recover_All, true},
              },
              {
                  {EventCode::Change_Items, true},
                  {EventCode::Change_Weapons, true},
                  {EventCode::Change_Armors, true},
                  {EventCode::Change_Party_Member, true},
                  {EventCode::Change_Nickname, true},
                  {EventCode::Change_Profile, true},
                  {EventCode::Change_Parameter, true},
              },
              {
                  {EventCode::Enemy_Recover_All, true},
                  {EventCode::Enemy_Appear, true},
                  {EventCode::Enemy_Transform, true},
                  {EventCode::Change_Enemy_HP, true},
                  {EventCode::Change_Enemy_MP, true},
                  {EventCode::Change_Enemy_TP, true},
                  {EventCode::Change_Enemy_State, true},
              },
          },
      },
      EventCommandTab{
          tr("Audio"),
          {
              {
                  {EventCode::Play_BGM, true},
                  {EventCode::Save_BGM, false},
                  {EventCode::Fadeout_BGM, true},
                  {EventCode::Resume_BGM, false, trNOOP("Resume BGM")},
              },
              {
                  {EventCode::Play_BGS, true},
                  {EventCode::Fadeout_BGS, true},
                  {EventCode::Play_Movie, true},
              },
              {
                  {EventCode::Play_ME, true},
                  {EventCode::Play_SE, true},
                  {EventCode::Stop_SE, false},
              },
          },
      },
      EventCommandTab{
          tr("Flow Control"),
          {
              {
                  {EventCode::Comment, true},
                  {EventCode::Conditional_Branch, true},
                  {EventCode::Loop, false},
                  {EventCode::Label, true},
                  {EventCode::Jump_To_Label, true},
                  {EventCode::Break_Loop, false},
                  {EventCode::Wait, true},
              },
              {
                  {EventCode::Control_Switches, true},
                  {EventCode::Control_Self_Switch, true},
                  {EventCode::Control_Variables, true},
                  {EventCode::Common_Event, true},
                  {EventCode::Control_Timer, true},
                  {EventCode::Erase_Event, false},
                  {EventCode::Exit_Event_Processing, false},
              },
              {
                  {EventCode::Transfer_Player, true},
                  {EventCode::Set_Event_Location, true},
                  {EventCode::Set_Vehicle_Location, true},
                  {EventCode::Script, true},
                  {EventCode::Plugin_Command_del_MV, true, {}, true, false},
                  {EventCode::Plugin_Command_del_MZ, true, {}, false, true},
                  {EventCode::Game_Over, false},
                  {EventCode::Return_To_Title_Screen, false},
              },
          },
      },
      EventCommandTab{
          tr("Scene"),
          {
              {
                  {EventCode::Show_Text, true},
                  {EventCode::Show_Choices, true},
                  {EventCode::Show_Scrolling_Text, true},
                  {EventCode::Input_Number, true},
                  {EventCode::Select_Item, true},
                  {EventCode::Set_Movement_Route, true},
              },
              {
                  {EventCode::Show_Animation, true},
                  {EventCode::Show_Battle_Animation, true},
                  {EventCode::Show_Balloon_Icon, true},
                  {EventCode::Shop_Processing, true},
                  {EventCode::Battle_Processing, true},
                  {EventCode::Name_Input_Processing, true},
              },
              {
                  {EventCode::Change_Transparency, true},
                  {EventCode::Get_on_fwsl_off_Vehicle},
                  {EventCode::Abort_Battle, false},
                  {EventCode::Gather_Followers, false},
              },
          },
      },
      EventCommandTab{
          trNOOP("Screen"),
          {
              {
                  {EventCode::Show_Picture, true},
                  {EventCode::Move_Picture, true},
                  {EventCode::Rotate_Picture, true},
                  {EventCode::Tint_Picture, true},
                  {EventCode::Erase_Picture, true},
              },
              {
                  {EventCode::Tint_Screen, true},
                  {EventCode::Flash_Screen, true},
                  {EventCode::Shake_Screen, true},
                  {EventCode::Fadeout_Screen, true},
                  {EventCode::Fadein_Screen, true},
              },
              {
                  {EventCode::Set_Weather_Effect, true},
                  {EventCode::Scroll_Map, true},
              },
          },
      },
      EventCommandTab{
          tr("System"),
          {
              {
                  {EventCode::Open_Menu_Screen, false},
                  {EventCode::Open_Save_Screen, false},
                  {EventCode::Change_Menu_Access, true},
                  {EventCode::Change_Save_Access, true},
                  {EventCode::Change_Formation_Access, true},
                  {EventCode::Change_Encounter, true},
              },
              {
                  {EventCode::Change_Battle_BGM, true},
                  {EventCode::Change_Vehicle_BGM, true},
                  {EventCode::Change_Victory_ME, true},
                  {EventCode::Change_Defeat_ME, true},
                  {EventCode::Change_Map_Display_Name, true},
                  {EventCode::Change_Parallax, true},
              },
              {
                  {EventCode::Change_Actor_Images, true},
                  {EventCode::Change_Tileset, true},
                  {EventCode::Change_Battle_Back, true},
                  {EventCode::Change_Vehicle_Image, true},
                  {EventCode::Change_Player_Followers, true},
                  {EventCode::Change_Window_Color, true},
              },
          },
      },
  };
}

float EventCommandEditor::findLargestButtonWidth(const std::list<EventCommandTab>& tabs) {
  float maxWidth = 0;

  for (const auto& [_, buttonColumns] : tabs) {
    for (const auto& column : buttonColumns) {
      for (const auto& button : column) {
        const auto label = (button.override.empty() ? DecodeEnumName(button.code) : button.override) + (button.needsElipses ? "..." : "");
        if (const auto width = ImGui::CalcTextSize(label.c_str()).x; width > maxWidth) {
          maxWidth = width;
        }
      }
    }
  }

  return maxWidth + (ImGui::GetStyle().ItemInnerSpacing.x * 2);
}

void EventCommandEditor::drawPopup() {
  if (m_selectedCommand < 0) {
    return;
  }
  if (!m_isRequested) {
    return;
  }

  const auto commandTabs = buildTabList();
  int numColumns = 0;
  int numButtons = 0;
  for (const auto& [_, buttonColumns] : commandTabs) {
    if (buttonColumns.size() > numColumns) {
      numColumns = buttonColumns.size();
      for (const auto& buttons : buttonColumns) {
        if (buttons.size() > numButtons) {
          numButtons = buttons.size();
        }
      }
    }
  }

  // numColumns++;
  const auto buttonWidth = findLargestButtonWidth(commandTabs);
  ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, {0.5f, 0.5f});
  ImGui::SetNextWindowSize(
      {buttonWidth * numColumns + (ImGui::GetStyle().FramePadding.x * (numColumns + 2)), ImGui::GetTextLineHeightWithSpacing() * (numButtons + 10) + (ImGui::GetStyle().FramePadding.y * 2)},
      ImGuiCond_Appearing);
  if (ImGui::BeginPopupModal(std::format("{}###command_picker", trNOOP("Command Window")).c_str(), nullptr,
                             ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize)) {
    bool close = false;
    ImGui::BeginVertical("##command_picker", ImGui::GetContentRegionAvail());
    {
      ImGui::BeginVertical("##command_picker_upper", {-1, -1});
      {
        // Event Templates
        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
        if (ImGui::BeginCombo("##eventcommand_editor_presets", trNOOP("Select a preset to insert into selection..."))) {
          int index{0};
          for (auto& templ : Database::instance()->templates.templates) {
            if (templ.type() == Template::TemplateType::Command) {
              if (!templ.commands().empty()) {
                if (ImGui::Selectable(templ.name().c_str(), false)) {

                  CommandParser parser;
                  nlohmann::ordered_json cmdJson = nlohmann::ordered_json::parse(templ.commands());
                  std::vector<std::shared_ptr<IEventCommand>> parsed = parser.parse(cmdJson);
                  for (const auto& command : parsed) {
                    if (command) {
                      command->adjustIndent(m_commands->at(m_selectedCommand)->indent().value());
                    }
                  }
                  if ((parsed.size() == 1 && parsed.at(0)->code() == EventCode::Event_Dummy) == false) {
                    m_commands->insert(m_commands->begin() + m_selectedCommand, parsed.begin(), parsed.end() - 1);
                  }
                  m_isNewEntry = false;
                  m_isRequested = false;
                  // ImGui::CloseCurrentPopup();
                }
                index++;
              } else {
                if (ImGui::Selectable(tr("Error loading template: " + templ.name()).c_str(), false)) {}
              }
            }
          }
          ImGui::EndCombo();
        }
        if (ImGui::BeginTabBar("##orpg_command_window")) {
          const auto size = ImVec2{buttonWidth, 0};

          for (int i = 0; const auto& tab : commandTabs) {
            drawCommandTab(tab, std::format("commandDialog_tab{}", i), size);
            ++i;
          }

          drawCommandDialog();
          ImGui::EndTabBar();
        }
      }
      ImGui::EndVertical();
      ImGui::Spring();
      ImGui::BeginVertical("##command_picker_lower", {-1, -1});
      {
        ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, ImGui::GetDPIScaledValue(1.5f));
        ImGui::BeginHorizontal("##buttons", {-1, -1});
        {
          ImGui::Spring();
          const int ret = ImGui::ButtonGroup("##action_buttons", {trNOOP("Cancel")});
          close = ret == 0;
        }
        ImGui::EndHorizontal();
      }
      ImGui::EndVertical();
    }
    ImGui::EndVertical();
    if (ImGui::IsKeyPressed(ImGuiKey_Escape) || close) {
      if (m_commandDialog) {
        m_commandDialog.reset();
      } else {
        m_isNewEntry = false;
        m_selectedCommand = 0;
        ImGui::CloseCurrentPopup();
      }
    }

    ImGui::EndPopup();
  } // End of "Command Window" Popup
}


code:
#pragma once

#include "Core/EventCommands/Dialog_AbortBattle.hpp"
#include "Core/EventCommands/Dialog_BattleProcessing.hpp"
#include "Core/EventCommands/Dialog_BreakLoop.hpp"
#include "Core/EventCommands/Dialog_ChangeActorImages.hpp"
#include "Core/EventCommands/Dialog_ChangeArmors.hpp"
#include "Core/EventCommands/Dialog_ChangeBattleBack.hpp"
#include "Core/EventCommands/Dialog_ChangeBattleBGM.hpp"
#include "Core/EventCommands/Dialog_ChangeClass.hpp"
#include "Core/EventCommands/Dialog_ChangeDefeatME.hpp"
#include "Core/EventCommands/Dialog_ChangeEncounter.hpp"
#include "Core/EventCommands/Dialog_ChangeEnemyHP.hpp"
#include "Core/EventCommands/Dialog_ChangeEnemyMP.hpp"
#include "Core/EventCommands/Dialog_ChangeEnemyState.hpp"
#include "Core/EventCommands/Dialog_ChangeEnemyTP.hpp"
#include "Core/EventCommands/Dialog_ChangeEquipment.hpp"
#include "Core/EventCommands/Dialog_ChangeEXP.hpp"
#include "Core/EventCommands/Dialog_ChangeFormationAccess.hpp"
#include "Core/EventCommands/Dialog_ChangeGold.hpp"
#include "Core/EventCommands/Dialog_ChangeHP.hpp"
#include "Core/EventCommands/Dialog_ChangeItems.hpp"
#include "Core/EventCommands/Dialog_ChangeLevel.hpp"
#include "Core/EventCommands/Dialog_ChangeMapDisplayName.hpp"
#include "Core/EventCommands/Dialog_ChangeMenuAccess.hpp"
#include "Core/EventCommands/Dialog_ChangeMP.hpp"
#include "Core/EventCommands/Dialog_ChangeName.hpp"
#include "Core/EventCommands/Dialog_ChangeNickname.hpp"
#include "Core/EventCommands/Dialog_ChangeParallax.hpp"
#include "Core/EventCommands/Dialog_ChangeParameter.hpp"
#include "Core/EventCommands/Dialog_ChangePartyMember.hpp"
#include "Core/EventCommands/Dialog_ChangePlayerFollowers.hpp"
#include "Core/EventCommands/Dialog_ChangePluginCommand.hpp"
#include "Core/EventCommands/Dialog_ChangeProfile.hpp"
#include "Core/EventCommands/Dialog_ChangeSaveAccess.hpp"
#include "Core/EventCommands/Dialog_ChangeSkill.hpp"
#include "Core/EventCommands/Dialog_ChangeState.hpp"
#include "Core/EventCommands/Dialog_ChangeTileset.hpp"
#include "Core/EventCommands/Dialog_ChangeTP.hpp"
#include "Core/EventCommands/Dialog_ChangeTransparency.hpp"
#include "Core/EventCommands/Dialog_ChangeVehicleBGM.hpp"
#include "Core/EventCommands/Dialog_ChangeVehicleImage.hpp"
#include "Core/EventCommands/Dialog_ChangeVictoryME.hpp"
#include "Core/EventCommands/Dialog_ChangeWeapons.hpp"
#include "Core/EventCommands/Dialog_ChangeWindowColor.hpp"
#include "Core/EventCommands/Dialog_Comment.hpp"
#include "Core/EventCommands/Dialog_CommonEvent.hpp"
#include "Core/EventCommands/Dialog_ConditionalBranch.hpp"
#include "Core/EventCommands/Dialog_ControlSelfSwitch.hpp"
#include "Core/EventCommands/Dialog_ControlSwitches.hpp"
#include "Core/EventCommands/Dialog_ControlTimer.hpp"
#include "Core/EventCommands/Dialog_ControlVariables.hpp"
#include "Core/EventCommands/Dialog_EnemyAppear.hpp"
#include "Core/EventCommands/Dialog_EnemyRecoverAll.hpp"
#include "Core/EventCommands/Dialog_EnemyTransform.hpp"
#include "Core/EventCommands/Dialog_EraseEvent.hpp"
#include "Core/EventCommands/Dialog_ErasePicture.hpp"
#include "Core/EventCommands/Dialog_ExitEventProcessing.hpp"
#include "Core/EventCommands/Dialog_FadeinScreen.hpp"
#include "Core/EventCommands/Dialog_FadeoutBGM.hpp"
#include "Core/EventCommands/Dialog_FadeoutBGS.hpp"
#include "Core/EventCommands/Dialog_FadeoutScreen.hpp"
#include "Core/EventCommands/Dialog_FlashScreen.hpp"
#include "Core/EventCommands/Dialog_GameOver.hpp"
#include "Core/EventCommands/Dialog_GatherFollowers.hpp"
#include "Core/EventCommands/Dialog_GetOnOffVehicle.hpp"
#include "Core/EventCommands/Dialog_InputNumber.hpp"
#include "Core/EventCommands/Dialog_JumpToLabel.hpp"
#include "Core/EventCommands/Dialog_Label.hpp"
#include "Core/EventCommands/Dialog_Loop.hpp"
#include "Core/EventCommands/Dialog_MovePicture.hpp"
#include "Core/EventCommands/Dialog_NameInputProcessing.hpp"
#include "Core/EventCommands/Dialog_OpenMenuScreen.hpp"
#include "Core/EventCommands/Dialog_OpenSaveScreen.hpp"
#include "Core/EventCommands/Dialog_PlayBGM.hpp"
#include "Core/EventCommands/Dialog_PlayBGS.hpp"
#include "Core/EventCommands/Dialog_PlayME.hpp"
#include "Core/EventCommands/Dialog_PlayMovie.hpp"
#include "Core/EventCommands/Dialog_PlaySE.hpp"
#include "Core/EventCommands/Dialog_RecoverAll.hpp"
#include "Core/EventCommands/Dialog_ReplayBGM.hpp"
#include "Core/EventCommands/Dialog_ReturnToTitle.hpp"
#include "Core/EventCommands/Dialog_RotatePicture.hpp"
#include "Core/EventCommands/Dialog_SaveBGM.hpp"
#include "Core/EventCommands/Dialog_Script.hpp"
#include "Core/EventCommands/Dialog_ScrollMap.hpp"
#include "Core/EventCommands/Dialog_SelectItem.hpp"
#include "Core/EventCommands/Dialog_SetEventLocation.hpp"
#include "Core/EventCommands/Dialog_SetMovementRoute.hpp"
#include "Core/EventCommands/Dialog_SetVehicleLocation.hpp"
#include "Core/EventCommands/Dialog_SetWeatherEffect.hpp"
#include "Core/EventCommands/Dialog_ShakeScreen.hpp"
#include "Core/EventCommands/Dialog_ShopProcessing.hpp"
#include "Core/EventCommands/Dialog_ShowAnimation.hpp"
#include "Core/EventCommands/Dialog_ShowBalloonIcon.hpp"
#include "Core/EventCommands/Dialog_ShowBattleAnimation.hpp"
#include "Core/EventCommands/Dialog_ShowChoice.hpp"
#include "Core/EventCommands/Dialog_ShowPicture.hpp"
#include "Core/EventCommands/Dialog_ShowScrollingText.hpp"
#include "Core/EventCommands/Dialog_ShowText.hpp"
#include "Core/EventCommands/Dialog_StopSE.hpp"
#include "Core/EventCommands/Dialog_TintPicture.hpp"
#include "Core/EventCommands/Dialog_TintScreen.hpp"
#include "Core/EventCommands/Dialog_TransferPlayer.hpp"
#include "Core/EventCommands/Dialog_Wait.hpp"
#include "Core/EventCommands/IEventDialogController.hpp"
#include "Database/EventCommands/BattleProcessing.hpp"
#include "Database/EventCommands/ChangeActorImage.hpp"
#include "Database/EventCommands/ChangeArmors.hpp"
#include "Database/EventCommands/ChangeClass.hpp"
#include "Database/EventCommands/ChangeEnemyHP.hpp"
#include "Database/EventCommands/ChangeEnemyMP.hpp"
#include "Database/EventCommands/ChangeEnemyState.hpp"
#include "Database/EventCommands/ChangeEnemyTP.hpp"
#include "Database/EventCommands/ChangeEquipment.hpp"
#include "Database/EventCommands/ChangeEXP.hpp"
#include "Database/EventCommands/ChangeHP.hpp"
#include "Database/EventCommands/ChangeItems.hpp"
#include "Database/EventCommands/ChangeLevel.hpp"
#include "Database/EventCommands/ChangeMP.hpp"
#include "Database/EventCommands/ChangeName.hpp"
#include "Database/EventCommands/ChangeNickname.hpp"
#include "Database/EventCommands/ChangeParameter.hpp"
#include "Database/EventCommands/ChangePartyMember.hpp"
#include "Database/EventCommands/ChangeProfile.hpp"
#include "Database/EventCommands/ChangeSkill.hpp"
#include "Database/EventCommands/ChangeState.hpp"
#include "Database/EventCommands/ChangeTileset.hpp"
#include "Database/EventCommands/ChangeTP.hpp"
#include "Database/EventCommands/ChangeWeapons.hpp"
#include "Database/EventCommands/CommonEvent.hpp"
#include "Database/EventCommands/ConditionalBranch.hpp"
#include "Database/EventCommands/EnemyAppear.hpp"
#include "Database/EventCommands/EnemyRecoverAll.hpp"
#include "Database/EventCommands/EnemyTransform.hpp"
#include "Database/EventCommands/ForceAction.hpp"
#include "Database/EventCommands/GetLocationInfo.hpp"
#include "Database/EventCommands/InputNumber.hpp"
#include "Database/EventCommands/NameInput.hpp"
#include "Database/EventCommands/RecoverAll.hpp"
#include "Database/EventCommands/Script.hpp"
#include "Database/EventCommands/SelectItem.hpp"
#include "Database/EventCommands/SetEventLocation.hpp"
#include "Database/EventCommands/SetMovementRoute.hpp"
#include "Database/EventCommands/SetVehicleLocation.hpp"
#include "Database/EventCommands/ShopProcessing.hpp"
#include "Database/EventCommands/ShowAnimation.hpp"
#include "Database/EventCommands/ShowBalloonIcon.hpp"
#include "Database/EventCommands/ShowBattleAnimation.hpp"
#include "Database/EventCommands/TransferPlayer.hpp"

#include <list>
#include <memory>
#include <vector>

struct IEventCommand;
struct MainWindow;
struct EventCommandEditor {
  struct EventCommandButton {
    EventCode code;
    bool needsElipses;
    std::string override;
    bool forMV = true;
    bool forMZ = true;
  };

  struct EventCommandTab {
    std::string title;
    std::list<std::list<EventCommandButton>> buttonColumns;
  };

  EventCommandEditor()
  : m_commandDialog(nullptr) {}
  void blockSelect(int n, bool isDelete);
  void handleClipboardInteraction();
  static void setupTableHeader();
  static void setupTableColors();
  void handleBlockCollapse(int& n) const;
  void draw();

  void setCommands(std::vector<std::shared_ptr<IEventCommand>>* commands) {
    m_commands = commands;
    m_selectedCommand = -1;
  }

  void setTroopId(int troopId) { m_troopId = troopId; }

private:
  static std::list<EventCommandTab> buildTabList();
  static float findLargestButtonWidth(const std::list<EventCommandTab>& tabs);
  void drawPopup();
  void drawCommandDialog();
  void drawCommandTab(const EventCommandTab& tab, const std::string&, ImVec2 size);

  bool m_isNewEntry{false};
  bool m_needsUpdate{false};
  bool m_isRequested{false};
  int m_currentTemplate{-1};
  int m_selectedCommand = {-1};
  int m_selectedEnd = -1;
  int m_hoveringCommand = -1;
  int m_hoveringEnd = -1;
  bool m_hasFocus{false};
  int m_troopId = 0;

  std::shared_ptr<IEventDialogController> m_commandDialog;
  std::vector<std::shared_ptr<IEventCommand>>* m_commands = nullptr;

  std::shared_ptr<IEventDialogController> CreateCommandDialog(EventCode code, std::shared_ptr<IEventCommand> cmd = nullptr) {
    switch (code) {
    case EventCode::Battle_Processing:
      return std::make_shared<Dialog_BattleProcessing>(DecodeEnumName(code), std::dynamic_pointer_cast<BattleProcessingCommand>(cmd));
    case EventCode::Event_Dummy:
      break;
    case EventCode::Show_Text:
      return std::make_shared<Dialog_ShowText>(DecodeEnumName(code), std::dynamic_pointer_cast<ShowTextCommand>(cmd));
    case EventCode::Next_Text:
      break;
    case EventCode::Show_Choices:
      return std::make_shared<Dialog_ShowChoice>(DecodeEnumName(code), std::dynamic_pointer_cast<ShowChoiceCommand>(cmd));
    case EventCode::When_Selected:
      break;
    case EventCode::When_Cancel:
      break;
    case EventCode::End_del_ShowChoices:
      break;
    case EventCode::Input_Number:
      return std::make_shared<Dialog_InputNumber>(DecodeEnumName(code), std::dynamic_pointer_cast<InputNumberCommand>(cmd));
    case EventCode::Select_Item:
      return std::make_shared<Dialog_SelectItem>(DecodeEnumName(code), std::dynamic_pointer_cast<SelectItemCommand>(cmd));
    case EventCode::Show_Scrolling_Text:
      return std::make_shared<Dialog_ShowScrollingText>(DecodeEnumName(code), std::dynamic_pointer_cast<ShowScrollTextCommand>(cmd));
    case EventCode::Next_Scrolling_Text:
      break;
    case EventCode::Comment:
      return std::make_shared<Dialog_Comment>(DecodeEnumName(code), std::dynamic_pointer_cast<CommentCommand>(cmd));
    case EventCode::Next_Comment:
      break;
    case EventCode::Conditional_Branch:
      return std::make_shared<Dialog_ConditionalBranch>(DecodeEnumName(code), std::dynamic_pointer_cast<ConditionalBranchCommand>(cmd), m_troopId);
    case EventCode::Else:
      break;
    case EventCode::End:
      break;
    case EventCode::Loop:
      return std::make_shared<Dialog_Loop>(DecodeEnumName(code), std::dynamic_pointer_cast<LoopCommand>(cmd));
    case EventCode::Repeat_Above:
      break;
    case EventCode::Break_Loop:
      return std::make_shared<Dialog_BreakLoop>(DecodeEnumName(code), std::dynamic_pointer_cast<BreakLoopCommand>(cmd));
    case EventCode::Exit_Event_Processing:
      return std::make_shared<Dialog_ExitEventProcessing>(DecodeEnumName(code), std::dynamic_pointer_cast<ExitEventProcessingCommand>(cmd));
    case EventCode::Common_Event:
      return std::make_shared<Dialog_CommonEvent>(DecodeEnumName(code), std::dynamic_pointer_cast<CommonEventCommand>(cmd));
    case EventCode::Label:
      return std::make_shared<Dialog_Label>(DecodeEnumName(code), std::dynamic_pointer_cast<LabelCommand>(cmd));
    case EventCode::Jump_To_Label:
      return std::make_shared<Dialog_JumpToLabel>(DecodeEnumName(code), std::dynamic_pointer_cast<JumpToLabelCommand>(cmd));
    case EventCode::Control_Switches:
      return std::make_shared<Dialog_ControlSwitches>(DecodeEnumName(code), std::dynamic_pointer_cast<ControlSwitches>(cmd));
    case EventCode::Control_Variables:
      return std::make_shared<Dialog_ControlVariables>(DecodeEnumName(code), std::dynamic_pointer_cast<ControlVariables>(cmd), m_troopId);
    case EventCode::Control_Self_Switch:
      return std::make_shared<Dialog_ControlSelfSwitch>(DecodeEnumName(code), std::dynamic_pointer_cast<ControlSelfSwitchCommand>(cmd));
    case EventCode::Control_Timer:
      return std::make_shared<Dialog_ControlTimer>(DecodeEnumName(code), std::dynamic_pointer_cast<ControlTimerCommand>(cmd));
    case EventCode::Change_Gold:
      return std::make_shared<Dialog_ChangeGold>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeGoldCommand>(cmd));
    case EventCode::Change_Items:
      return std::make_shared<Dialog_ChangeItems>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeItemsCommand>(cmd));
    case EventCode::Change_Weapons:
      return std::make_shared<Dialog_ChangeWeapons>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeWeaponsCommand>(cmd));
    case EventCode::Change_Armors:
      return std::make_shared<Dialog_ChangeArmors>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeArmorsCommand>(cmd));
    case EventCode::Change_Party_Member:
      return std::make_shared<Dialog_ChangePartyMember>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangePartyMemberCommand>(cmd));
    case EventCode::Change_Battle_BGM:
      return std::make_shared<Dialog_ChangeBattleBGM>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeBattleBGMCommand>(cmd));
    case EventCode::Change_Victory_ME:
      return std::make_shared<Dialog_ChangeVictoryME>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeVictoryMECommand>(cmd));
    case EventCode::Change_Save_Access:
      return std::make_shared<Dialog_ChangeSaveAccess>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeSaveAccessCommand>(cmd));
    case EventCode::Change_Menu_Access:
      return std::make_shared<Dialog_ChangeMenuAccess>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeMenuAccessCommand>(cmd));
    case EventCode::Change_Encounter:
      return std::make_shared<Dialog_ChangeEncounter>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeEncounterDisableCommand>(cmd));
    case EventCode::Change_Formation_Access:
      return std::make_shared<Dialog_ChangeFormationAccess>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeFormationAccessCommand>(cmd));
    case EventCode::Change_Window_Color:
      return std::make_shared<Dialog_ChangeWindowColor>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeWindowColorCommand>(cmd));
    case EventCode::Change_Defeat_ME:
      return std::make_shared<Dialog_ChangeDefeatME>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeDefeatMECommand>(cmd));
    case EventCode::Change_Vehicle_BGM:
      return std::make_shared<Dialog_ChangeVehicleBGM>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeVehicleBGMCommand>(cmd));
    case EventCode::Transfer_Player:
      return std::make_shared<Dialog_TransferPlayer>(DecodeEnumName(code), std::dynamic_pointer_cast<TransferPlayerCommand>(cmd));
    case EventCode::Set_Vehicle_Location:
      return std::make_shared<Dialog_SetVehicleLocation>(DecodeEnumName(code), std::dynamic_pointer_cast<SetVehicleLocationCommand>(cmd));
    case EventCode::Set_Event_Location:
      return std::make_shared<Dialog_SetEventLocation>(DecodeEnumName(code), std::dynamic_pointer_cast<SetEventLocationCommand>(cmd));
    case EventCode::Scroll_Map:
      return std::make_shared<Dialog_ScrollMap>(DecodeEnumName(code), std::dynamic_pointer_cast<ScrollMapCommand>(cmd));
    case EventCode::Set_Movement_Route:
      return std::make_shared<Dialog_SetMovementRoute>(DecodeEnumName(code), std::dynamic_pointer_cast<SetMovementRouteCommand>(cmd));
    case EventCode::Movement_Route_Step:
      break;
    case EventCode::Get_on_fwsl_off_Vehicle:
      return std::make_shared<Dialog_GetOnOffVehicle>(DecodeEnumName(code), std::dynamic_pointer_cast<GetOnOffVehicleCommand>(cmd));
    case EventCode::Change_Transparency:
      return std::make_shared<Dialog_ChangeTransparency>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeTransparencyCommand>(cmd));
    case EventCode::Show_Animation:
      return std::make_shared<Dialog_ShowAnimation>(DecodeEnumName(code), std::dynamic_pointer_cast<ShowAnimationCommand>(cmd));
    case EventCode::Show_Balloon_Icon:
      return std::make_shared<Dialog_ShowBalloonIcon>(DecodeEnumName(code), std::dynamic_pointer_cast<ShowBalloonIconCommand>(cmd));
    case EventCode::Erase_Event:
      return std::make_shared<Dialog_EraseEvent>(DecodeEnumName(code), std::dynamic_pointer_cast<EraseEventCommand>(cmd));
    case EventCode::Change_Player_Followers:
      return std::make_shared<Dialog_ChangePlayerFollowers>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangePlayerFollowersCommand>(cmd));
    case EventCode::Gather_Followers:
      return std::make_shared<Dialog_GatherFollowers>(DecodeEnumName(code), std::dynamic_pointer_cast<GatherFollowersCommand>(cmd));
    case EventCode::Fadeout_Screen:
      return std::make_shared<Dialog_FadeoutScreen>(DecodeEnumName(code), std::dynamic_pointer_cast<FadeoutScreenCommand>(cmd));
    case EventCode::Fadein_Screen:
      return std::make_shared<Dialog_FadeinScreen>(DecodeEnumName(code), std::dynamic_pointer_cast<FadeinScreenCommand>(cmd));
    case EventCode::Tint_Screen:
      return std::make_shared<Dialog_TintScreen>(DecodeEnumName(code), std::dynamic_pointer_cast<TintScreenCommand>(cmd));
    case EventCode::Flash_Screen:
      return std::make_shared<Dialog_FlashScreen>(DecodeEnumName(code), std::dynamic_pointer_cast<FlashScreenCommand>(cmd));
    case EventCode::Shake_Screen:
      return std::make_shared<Dialog_ShakeScreen>(DecodeEnumName(code), std::dynamic_pointer_cast<ShakeScreenCommand>(cmd));
    case EventCode::Wait:
      return std::make_shared<Dialog_Wait>(DecodeEnumName(code), std::dynamic_pointer_cast<WaitCommand>(cmd));
    case EventCode::Show_Picture:
      return std::make_shared<Dialog_ShowPicture>(DecodeEnumName(code), std::dynamic_pointer_cast<ShowPictureCommand>(cmd));
    case EventCode::Move_Picture:
      return std::make_shared<Dialog_MovePicture>(DecodeEnumName(code), std::dynamic_pointer_cast<MovePictureCommand>(cmd));
    case EventCode::Rotate_Picture:
      return std::make_shared<Dialog_RotatePicture>(DecodeEnumName(code), std::dynamic_pointer_cast<RotatePictureCommand>(cmd));
    case EventCode::Tint_Picture:
      return std::make_shared<Dialog_TintPicture>(DecodeEnumName(code), std::dynamic_pointer_cast<TintPictureCommand>(cmd));
    case EventCode::Erase_Picture:
      return std::make_shared<Dialog_ErasePicture>(DecodeEnumName(code), std::dynamic_pointer_cast<ErasePictureCommand>(cmd));
    case EventCode::Set_Weather_Effect:
      return std::make_shared<Dialog_SetWeatherEffect>(DecodeEnumName(code), std::dynamic_pointer_cast<SetWeatherEffectCommand>(cmd));
    case EventCode::Play_BGM:
      return std::make_shared<Dialog_PlayBGM>(DecodeEnumName(code), std::dynamic_pointer_cast<PlayBGMCommand>(cmd));
    case EventCode::Fadeout_BGM:
      return std::make_shared<Dialog_FadeoutBGM>(DecodeEnumName(code), std::dynamic_pointer_cast<FadeoutBGM>(cmd));
    case EventCode::Save_BGM:
      return std::make_shared<Dialog_SaveBGM>(DecodeEnumName(code), std::dynamic_pointer_cast<SaveBGMCommand>(cmd));
    case EventCode::Resume_BGM:
      return std::make_shared<Dialog_ReplayBGM>(DecodeEnumName(code), std::dynamic_pointer_cast<ResumeBGMCommand>(cmd));
    case EventCode::Play_BGS:
      return std::make_shared<Dialog_PlayBGS>(DecodeEnumName(code), std::dynamic_pointer_cast<PlayBGSCommand>(cmd));
    case EventCode::Fadeout_BGS:
      return std::make_shared<Dialog_FadeoutBGS>(DecodeEnumName(code), std::dynamic_pointer_cast<FadeoutBGS>(cmd));
    case EventCode::Play_ME:
      return std::make_shared<Dialog_PlayME>(DecodeEnumName(code), std::dynamic_pointer_cast<PlayMECommand>(cmd));
    case EventCode::Play_SE:
      return std::make_shared<Dialog_PlaySE>(DecodeEnumName(code), std::dynamic_pointer_cast<PlaySECommand>(cmd));
    case EventCode::Stop_SE:
      return std::make_shared<Dialog_StopSE>(DecodeEnumName(code), std::dynamic_pointer_cast<StopSECommand>(cmd));
    case EventCode::Play_Movie:
      return std::make_shared<Dialog_PlayMovie>(DecodeEnumName(code), std::dynamic_pointer_cast<PlayMovieCommand>(cmd));
    case EventCode::Change_Map_Display_Name:
      return std::make_shared<Dialog_ChangeMapDisplayName>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeMapNameDisplayCommand>(cmd));
    case EventCode::Change_Tileset:
      return std::make_shared<Dialog_ChangeTileset>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeTilesetCommand>(cmd));
    case EventCode::Change_Battle_Back:
      return std::make_shared<Dialog_ChangeBattleBack>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeBattlebackCommand>(cmd));
    case EventCode::Change_Parallax:
      return std::make_shared<Dialog_ChangeParallax>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeParallaxCommand>(cmd));
    case EventCode::Get_Location_Info:
      // return std::make_shared<Dialog_GetLocationInfo>(DecodeEnumName(code),
      // std::dynamic_pointer_cast<GetLocationInfoCommand>(cmd));
      break;
    case EventCode::If_Win:
      break;
    case EventCode::If_Escape:
      break;
    case EventCode::If_Lose:
      break;
    case EventCode::End_del_Battle_Processing:
      break;
    case EventCode::Shop_Processing:
      return std::make_shared<Dialog_ShopProcessing>(DecodeEnumName(code), std::dynamic_pointer_cast<ShopProcessingCommand>(cmd));
    case EventCode::Shop_Processing_Good:
      break;
    case EventCode::Name_Input_Processing:
      return std::make_shared<Dialog_NameInputProcessing>(DecodeEnumName(code), std::dynamic_pointer_cast<NameInputCommand>(cmd));
    case EventCode::Change_HP:
      return std::make_shared<Dialog_ChangeHP>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeHPCommand>(cmd));
    case EventCode::Change_MP:
      return std::make_shared<Dialog_ChangeMP>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeMPCommand>(cmd));
    case EventCode::Change_State:
      return std::make_shared<Dialog_ChangeState>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeStateCommand>(cmd));
    case EventCode::Recover_All:
      return std::make_shared<Dialog_RecoverAll>(DecodeEnumName(code), std::dynamic_pointer_cast<RecoverAllCommand>(cmd));
    case EventCode::Change_EXP:
      return std::make_shared<Dialog_ChangeEXP>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeEXPCommand>(cmd));
    case EventCode::Change_Level:
      return std::make_shared<Dialog_ChangeLevel>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeLevelCommand>(cmd));
    case EventCode::Change_Parameter:
      return std::make_shared<Dialog_ChangeParameter>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeParameterCommand>(cmd));
    case EventCode::Change_Skill:
      return std::make_shared<Dialog_ChangeSkill>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeSkillCommand>(cmd));
    case EventCode::Change_Equipment:
      return std::make_shared<Dialog_ChangeEquipment>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeEquipmentCommand>(cmd));
    case EventCode::Change_Name:
      return std::make_shared<Dialog_ChangeName>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeNameCommand>(cmd));
    case EventCode::Change_Class:
      return std::make_shared<Dialog_ChangeClass>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeClassCommand>(cmd));
    case EventCode::Change_Actor_Images:
      return std::make_shared<Dialog_ChangeActorImages>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeActorImageCommand>(cmd));
    case EventCode::Change_Vehicle_Image:
      return std::make_shared<Dialog_ChangeVehicleImage>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeVehicleImageCommand>(cmd));
    case EventCode::Change_Nickname:
      return std::make_shared<Dialog_ChangeNickname>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeNicknameCommand>(cmd));
    case EventCode::Change_Profile:
      return std::make_shared<Dialog_ChangeProfile>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeProfileCommand>(cmd));
    case EventCode::Change_TP:
      return std::make_shared<Dialog_ChangeTP>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeTPCommand>(cmd));
    case EventCode::Enemy_Recover_All:
      return std::make_shared<Dialog_EnemyRecoverAll>(DecodeEnumName(code), std::dynamic_pointer_cast<EnemyRecoverAllCommand>(cmd), m_troopId);
    case EventCode::Enemy_Appear:
      return std::make_shared<Dialog_EnemyAppear>(DecodeEnumName(code), std::dynamic_pointer_cast<EnemyAppearCommand>(cmd), m_troopId);
    case EventCode::Enemy_Transform:
      return std::make_shared<Dialog_EnemyTransform>(DecodeEnumName(code), std::dynamic_pointer_cast<EnemyTransformCommand>(cmd), m_troopId);
    case EventCode::Change_Enemy_HP:
      return std::make_shared<Dialog_ChangeEnemyHP>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeEnemyHPCommand>(cmd), m_troopId);
    case EventCode::Change_Enemy_MP:
      return std::make_shared<Dialog_ChangeEnemyMP>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeEnemyMPCommand>(cmd), m_troopId);
    case EventCode::Change_Enemy_TP:
      return std::make_shared<Dialog_ChangeEnemyTP>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeEnemyTPCommand>(cmd), m_troopId);
    case EventCode::Change_Enemy_State:
      return std::make_shared<Dialog_ChangeEnemyState>(DecodeEnumName(code), std::dynamic_pointer_cast<ChangeEnemyStateCommand>(cmd), m_troopId);
    case EventCode::Show_Battle_Animation:
      return std::make_shared<Dialog_ShowBattleAnimation>(DecodeEnumName(code), std::dynamic_pointer_cast<ShowBattleAnimationCommand>(cmd), m_troopId);
    case EventCode::Force_Action:
      // return std::make_shared<Dialog_ForceAction>(DecodeEnumName(code),
      // std::dynamic_pointer_cast<ForceActionCommand>(cmd));
      break;
    case EventCode::Abort_Battle:
      return std::make_shared<Dialog_AbortBattle>(DecodeEnumName(code), std::dynamic_pointer_cast<AbortBattleCommand>(cmd));
    case EventCode::Open_Menu_Screen:
      return std::make_shared<Dialog_OpenMenuScreen>(DecodeEnumName(code), std::dynamic_pointer_cast<OpenMenuCommand>(cmd));
    case EventCode::Open_Save_Screen:
      return std::make_shared<Dialog_OpenSaveScreen>(DecodeEnumName(code), std::dynamic_pointer_cast<OpenSaveCommand>(cmd));
    case EventCode::Game_Over:
      return std::make_shared<Dialog_GameOver>(DecodeEnumName(code), std::dynamic_pointer_cast<GameOverCommand>(cmd));
    case EventCode::Return_To_Title_Screen:
      return std::make_shared<Dialog_ReturnToTitle>(DecodeEnumName(code), std::dynamic_pointer_cast<ReturnToTitleCommand>(cmd));
    case EventCode::Script:
      return std::make_shared<Dialog_Script>(DecodeEnumName(code), std::dynamic_pointer_cast<ScriptCommand>(cmd));
    case EventCode::Next_Script:
      break;
    case EventCode::Plugin_Command_del_MV:
      return std::make_shared<Dialog_ChangePluginCommand>(DecodeEnumName(code), std::dynamic_pointer_cast<PluginCommandMV>(cmd));
    case EventCode::Plugin_Command_del_MZ:
      break;
    case EventCode::Next_PluginMZ_Command:
      break;
    case EventCode::Move_Down:
      break;
    case EventCode::Move_Left:
      break;
    case EventCode::Move_Right:
      break;
    case EventCode::Move_Up:
      break;
    case EventCode::Move_Lower_Left:
      break;
    case EventCode::Move_Lower_Right:
      break;
    case EventCode::Move_Upper_Left:
      break;
    case EventCode::Move_Upper_Right:
      break;
    case EventCode::Move_at_Random:
      break;
    case EventCode::Move_toward_Player:
      break;
    case EventCode::Move_away_from_Player:
      break;
    case EventCode::_1_Step_Forward:
      break;
    case EventCode::_1_Step_Backward:
      break;
    case EventCode::Jump:
      break;
    case EventCode::Wait_del_Movement:
      break;
    case EventCode::Turn_Down:
      break;
    case EventCode::Turn_Left:
      break;
    case EventCode::Turn_Right:
      break;
    case EventCode::Turn_Up:
      break;
    case EventCode::Turn_90_deg_Right:
      break;
    case EventCode::Turn_90_deg_Left:
      break;
    case EventCode::Turn_180_deg:
      break;
    case EventCode::Turn_90_deg_Left_or_Right:
      break;
    case EventCode::Turn_at_Random:
      break;
    case EventCode::Turn_toward_Player:
      break;
    case EventCode::Turn_away_from_Player:
      break;
    case EventCode::Switch_ON:
      break;
    case EventCode::Switch_OFF:
      break;
    case EventCode::Speed:
      break;
    case EventCode::Frequency:
      break;
    case EventCode::Walking_Animation_ON:
      break;
    case EventCode::Walking_Animation_OFF:
      break;
    case EventCode::Stepping_Animation_ON:
      break;
    case EventCode::Stepping_Animation_OFF:
      break;
    case EventCode::Direction_Fix_ON:
      break;
    case EventCode::Direction_Fix_OFF:
      break;
    case EventCode::Through_ON:
      break;
    case EventCode::Through_OFF:
      break;
    case EventCode::Transparent_ON:
      break;
    case EventCode::Transparent_OFF:
      break;
    case EventCode::Change_Image:
      break;
    case EventCode::Change_Opacity:
      break;
    case EventCode::Change_Blend_Mode:
      break;
    case EventCode::Play_SE_del_Movement:
      break;
    case EventCode::Script_del_Movement:
      break;
    }
    return {};
  }
};

repo_name: Antidote/openrpgeditor

license: mit


code:
#include "Core/CommonUI/EventTilePicker.hpp"

#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "GroupBox.hpp"

#include "Database/Database.hpp"
#include "orei18n.hpp"

#include <Database/MapInfos.hpp>
#include <imgui.h>
#include <imgui_internal.h>
EventTilePicker::EventTilePicker(const int mapId, const bool showTree)
: IDialogController(trNOOP("Select Location"))
, m_currentMap(mapId)
, m_showTree(showTree) {

  for (const auto& mapInfo : Database::instance()->mapInfos.mapInfos()) {
    if (!mapInfo) {
      continue;
    }

    m_expandedState[mapInfo->id()] = mapInfo->expanded();
  }
}

std::tuple<bool, bool> EventTilePicker::draw() {
  if (isOpen()) {
    ImGui::OpenPopup("###EventTilePicker");
  }

  ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
  const auto maxSize = ImVec2{(ImGui::CalcTextSize("#").x * 56) + (ImGui::GetStyle().FramePadding.x * 2), (ImGui::GetFrameHeightWithSpacing() * 16) + (ImGui::GetStyle().FramePadding.y * 2)};
  ImGui::SetNextWindowSize(maxSize, ImGuiCond_Appearing);
  ImGui::SetNextWindowSizeConstraints(maxSize, {FLT_MAX, FLT_MAX});

  if (ImGui::BeginPopupModal(std::format("{}###EventTilePicker", tr(m_name)).c_str())) {
    ImGui::BeginVertical("##event_tile_picker_main_layout", ImGui::GetContentRegionAvail(), 0);
    {
      ImGui::BeginHorizontal("##event_tile_picker_main_layout", {-1, 0});
      {
        if (m_showTree) {
          ImGui::BeginChild("##event_tile_picker_map_tree", {ImGui::GetContentRegionAvail().x * .30f, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()},
                            ImGuiChildFlags_ResizeX | ImGuiChildFlags_Borders, ImGuiWindowFlags_NoBackground);
          { drawMapTreeRecursive(Database::instance()->mapInfos.root()); }
          ImGui::EndChild();
        }
        ImGui::BeginChild("##event_tile_picker_map_canvas", {-1, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()}, ImGuiChildFlags_Borders,
                          ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_HorizontalScrollbar);
        {
          // TODO: Replace all of this with the tilemap canvas when that is rewritten
          const auto map = Database::instance()->mapInfos.map(m_currentMap);
          std::string nameStr = map ? map->id() == 0 ? Database::instance()->system.gameTitle() : map->name() : "No Valid Map Selected";

          if (nameStr.empty()) {
            if (map) {
              if (map->id() != 0) {
                nameStr = std::format("Map{:03}", map->id());
              } else {
                nameStr = map->name();
              }
            } else {
              nameStr = std::format("No Valid Map Selected");
            }
          }
          ImGui::TextUnformatted("PLACEHOLDER DIALOG!");
          ImGui::Text("Map: %s\n", nameStr.c_str());
          GroupBox position("Position", "##event_tile_picker_position", {0, 0});
          if (position.begin()) {
            ImGui::BeginVertical("##event_tile_picker_position", {-1, 0});
            {
              ImGui::BeginHorizontal("##event_tile_picker_x", {-1, 0});
              {
                GroupBox x("X", "##event_tile_picker_position_y", {0, 0});
                if (x.begin()) {
                  if (ImGui::SpinInt("##event_tile_picker_x", &m_selectedTile.xr(), 1, 1)) {
                    m_selectedTile.xr() = std::clamp(m_selectedTile.xr(), 0, map ? map->map()->width() : 0);
                  }
                }
                x.end();
              }
              ImGui::EndHorizontal();
              ImGui::BeginHorizontal("##event_tile_picker_y", {-1, 0});
              {
                GroupBox y("Y", "##event_tile_picker_position_y", {0, 0});
                if (y.begin()) {
                  if (ImGui::SpinInt("##event_tile_picker_y", &m_selectedTile.yr(), 1, 1)) {
                    m_selectedTile.yr() = std::clamp(m_selectedTile.yr(), 0, map ? map->map()->height() : 0);
                  }
                }
                y.end();
              }
              ImGui::EndHorizontal();
            }
            ImGui::EndVertical();
          }
          position.end();
        }
        ImGui::EndChild();
      }
      ImGui::EndHorizontal();

      ImGui::Spring();
      ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, ImGui::GetDPIScaledValue(1.5f));
      ImGui::BeginHorizontal("##event_tile_picker_button_layout", {-1, 0}, 0);
      {
        ImGui::Spring();
        if (const auto ret = ImGui::ButtonGroup("##event_tile_picker_buttons", {trNOOP("OK"), trNOOP("Cancel")}); ret == 0) {
          m_confirmed = true;
          setOpen(false);
          ImGui::CloseCurrentPopup();
        } else if (ret == 1) {
          m_confirmed = false;
          setOpen(false);
          ImGui::CloseCurrentPopup();
        }
      }
      ImGui::EndHorizontal();
    }
    ImGui::EndVertical();
    ImGui::EndPopup();
  }

  return {!m_open, m_confirmed};
}

void EventTilePicker::drawMapTreeRecursive(MapInfo& info) {
  const std::string id = std::format("#event_tile_picker_map{:03}_node", info.id());
  std::string nameStr = info.id() == 0 ? Database::instance()->system.gameTitle() : info.name();

  if (nameStr.empty()) {
    nameStr = info.name();
  }

  const bool open = ImGui::TreeNodeEx(id.c_str(),
                                      (m_expandedState[info.id()] ? ImGuiTreeNodeFlags_DefaultOpen : 0) | (info.children().empty() ? ImGuiTreeNodeFlags_Leaf : 0) |
                                          (m_currentMap == info.id() ? ImGuiTreeNodeFlags_Selected : 0) | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick,
                                      "%s", nameStr.c_str());

  if (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemFocused() && m_currentMap != info.id()) {
    m_currentMap = info.id();
    info.map(true);
  }

  if (open) {
    for (const auto& mapInfo : info.children()) {
      if (!mapInfo) {
        continue;
      }

      drawMapTreeRecursive(*mapInfo);
    }
  }

  if (ImGui::IsItemToggledOpen()) {
    m_expandedState[info.id()] ^= 1;
  }

  if (open) {
    ImGui::TreePop();
  }
}


code:
#pragma once
#include "Core/CommonUI/IDialogController.hpp"
#include "OREMath/Point.hpp"

#include <map>

class MapInfo;
class EventTilePicker final : public IDialogController {
public:
  explicit EventTilePicker(int mapId, bool showTree = true);

  std::tuple<bool, bool> draw() override;

  Point selectedTile() const { return m_selectedTile; }
  int selectedMap() const { return m_currentMap; }

private:
  void drawMapTreeRecursive(MapInfo& info);

  int m_currentMap;
  Point m_selectedTile;
  bool m_showTree{true};

  std::map<int, bool> m_expandedState; // mapId -> expanded
};


code:
#include "Core/CommonUI/FacePicker.hpp"

#include <format>
#include <imgui.h>
#include <imgui_internal.h>

#include <IconsFontAwesome6.h>

FacePicker::FacePicker(const std::string_view sheetName, const int faceIndex)
: IDialogController("Face")
, m_faceDirectory("img/faces/", ".png", std::string(sheetName)) {
  if (!sheetName.empty()) {
    m_faceSheet.emplace(sheetName);
  }

  m_faceSheets = m_faceDirectory.getDirectoryContents();
  m_folderDir = m_faceDirectory.getDirectories();
  setFaceInfo(sheetName, faceIndex);
}

void FacePicker::setFaceInfo(const std::string_view sheetName, const int faceIndex) {
  m_faceSheet.emplace(sheetName);
  m_faceIndex = faceIndex;
  const std::string imageName = m_faceDirectory.getFileName(static_cast<std::string>(sheetName));

  if (!imageName.empty()) {
    bool found = false;
    for (int i = 0; i < m_faceSheets.size(); ++i) {
      if (!m_faceSheets[i].compare(sheetName)) {
        found = true;
        m_selectedSheet = i;
        break;
      }
    }

    if (!found) {
      m_faceSheet.reset();
      m_selectedSheet = -1;
      return;
    }

    const auto [min, _] = m_faceSheet->getFaceRect(m_faceIndex);
    m_selectionX = min.x() * m_faceSheet->texture().width();
    m_selectionY = min.y() * m_faceSheet->texture().height();
  }

  if (m_faceSheet) {
    m_checkerboardTexture.setSize(m_faceSheet->texture().width(), m_faceSheet->texture().height());
  }
}

std::tuple<bool, bool> FacePicker::draw() {
  if (isOpen()) {
    ImGui::OpenPopup(m_name.c_str());
  }

  const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
  ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
  ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size / 2, ImGuiCond_Appearing);

  if (ImGui::BeginPopupModal(m_name.c_str(), &m_open, ImGuiWindowFlags_NoSavedSettings)) {
    const auto calc = ImGui::CalcTextSize("OKCANCEL");
    ImGui::BeginChild("##top_child", {0, ImGui::GetContentRegionAvail().y - (calc.y + (ImGui::GetStyle().ItemSpacing.y * 3) + ImGui::GetStyle().FramePadding.y)});
    {
      ImGui::BeginChild("##image_picker_list", ImVec2{ImGui::CalcTextSize("ABCDEFGHIJKLMNOPQRS").x, ImGui::GetContentRegionAvail().y - ImGui::GetStyle().FramePadding.y},
                        ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX);
      {
        if (ImGui::BeginTable("##image_picker.tablelist", 1)) {
          ImGui::TableNextRow();
          ImGui::TableNextColumn();

          ImGui::BeginDisabled(m_faceDirectory.isParentDirectory());
          if (ImGui::Selectable("\u21B0 ..", false, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
            if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
              m_faceDirectory.moveUp();
              m_faceSheets = m_faceDirectory.getDirectoryContents();
              m_folderDir = m_faceDirectory.getDirectories();
            }
          }
          ImGui::EndDisabled();

          for (int i = 0; i < m_folderDir.size(); ++i) {
            const auto& folderName = std::format("{} {}", ICON_FA_FOLDER_OPEN, m_folderDir[i]);
            if (ImGui::Selectable(folderName.c_str(), m_selectedFolder == i, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
              m_selectedFolder = i;
            }
            if (m_selectedFolder == i && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
              m_faceDirectory.setDirectory(i);
              m_folderDir = m_faceDirectory.getDirectories();
              m_faceSheets = m_faceDirectory.getDirectoryContents();
              m_selectedFolder = -1;
              m_selectedSheet = -1;
              m_faceSheet.reset();
            }
          }

          if (ImGui::Selectable("(None)", m_selectedSheet < 0, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
            m_selectedSheet = -1;
            m_faceSheet.reset();
          }

          for (int i = 0; i < m_faceSheets.size(); ++i) {
            const auto& sheet = m_faceSheets[i];
            ImGui::TableNextColumn();
            if (ImGui::Selectable(sheet.c_str(), m_selectedSheet == i, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
              m_selectedSheet = i;
              m_faceSheet.emplace(m_selectedSheet == -1                 ? ""
                                  : m_faceDirectory.isParentDirectory() ? m_faceSheets.at(m_selectedSheet)
                                                                        : m_faceDirectory.getPathPrefix() + '/' + m_faceSheets.at(m_selectedSheet));
              m_checkerboardTexture.setSize(m_faceSheet->texture().width(), m_faceSheet->texture().height());

              if (ImGui::IsItemHovered()) {
                ImGui::SetTooltip("%s", sheet.c_str());
              }
            }
            if (m_selectedSheet == i && ImGui::IsWindowAppearing()) {
              ImGui::SetScrollHereY();
            }
          }
          ImGui::EndTable();
        }
        ImGui::EndChild();
        ImGui::SameLine();
        ImGui::BeginChild("##image_picker_image_panel");
        {
          auto win = ImGui::GetCurrentWindow();
          if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && ImGui::IsWindowHovered()) {
            auto mouseCursor = ImGui::GetMousePos();
            mouseCursor -= win->ContentRegionRect.Min;
            int x = alignValue(mouseCursor.x, FaceSheet::faceWidth());
            int y = alignValue(mouseCursor.y, FaceSheet::faceHeight());
            if ((x >= 0 && x < m_faceSheet->texture().width()) && (y >= 0 && y < m_faceSheet->texture().height())) {
              x /= FaceSheet::faceWidth();
              y /= FaceSheet::faceHeight();

              m_faceIndex = (y * (m_faceSheet->texture().width() / FaceSheet::faceWidth())) + x;
              const auto [min, _] = m_faceSheet->getFaceRect(m_faceIndex);
              m_selectionX = min.x() * m_faceSheet->texture().width();
              m_selectionY = min.y() * m_faceSheet->texture().height();
            }
          }

          if (m_faceSheet && m_selectedSheet >= 0) {
            ImGui::Dummy({static_cast<float>(m_faceSheet->texture().width()), static_cast<float>(m_faceSheet->texture().height())});
            win->DrawList->AddImage(static_cast<ImTextureID>(m_checkerboardTexture), win->ContentRegionRect.Min + ImVec2{0.f, 0.f},
                                    win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_checkerboardTexture.width()), static_cast<float>(m_checkerboardTexture.height())}));
            win->DrawList->AddImage(m_faceSheet->texture(), win->ContentRegionRect.Min + ImVec2{0.f, 0.f},
                                    win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_faceSheet->texture().width()), static_cast<float>(m_faceSheet->texture().height())}));
            win->DrawList->AddRect(win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_selectionX), static_cast<float>(m_selectionY)}),
                                   win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_selectionX) + static_cast<float>(FaceSheet::faceWidth()),
                                                                        static_cast<float>(m_selectionY) + static_cast<float>(FaceSheet::faceHeight())}),
                                   0xFF000000, 0.f, 0, 7.f);
            win->DrawList->AddRect(win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_selectionX), static_cast<float>(m_selectionY)}),
                                   win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_selectionX) + static_cast<float>(FaceSheet::faceWidth()),
                                                                        static_cast<float>(m_selectionY) + static_cast<float>(FaceSheet::faceHeight())}),
                                   0xFFFFFFFF, 0.f, 0, 3.f);
          }
        }
        ImGui::EndChild();
      }
      ImGui::EndChild();
      ImGui::BeginChild("##bottom_child");
      {
        ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - (calc.x + (ImGui::GetStyle().FramePadding.x * 2) + ImGui::GetStyle().ItemSpacing.x));
        if (ImGui::Button("OK")) {
          m_confirmed = true;
          m_open = false;
          ImGui::CloseCurrentPopup();
        }
        ImGui::SameLine();
        if (ImGui::Button("Cancel")) {
          m_confirmed = false;
          m_open = false;
          ImGui::CloseCurrentPopup();
        }
      }
      ImGui::EndChild();
      ImGui::EndPopup();
    }
  }
  return {!m_open, m_confirmed};
}


code:
#pragma once
#include "Core/CommonUI/IDialogController.hpp"
#include "Core/Graphics/CheckerboardTexture.hpp"
#include "Core/Graphics/FaceSheet.hpp"
#include "Directory.hpp"

#include <optional>

class FacePicker final : public IDialogController {
public:
  explicit FacePicker(std::string_view sheetName = {}, int faceIndex = 0);

  std::tuple<bool, bool> draw() override;

  [[nodiscard]] std::string selectedSheet() const {
    return m_selectedSheet >= 0 ? m_faceDirectory.isParentDirectory() ? m_faceSheets[m_selectedSheet] : m_faceDirectory.getPathPrefix() + '/' + m_faceSheets[m_selectedSheet] : "";
  }

  [[nodiscard]] int faceIndex() const { return m_faceIndex; }

  void setFaceInfo(std::string_view faceName, int faceIndex);

private:
  std::optional<FaceSheet> m_faceSheet;
  std::vector<std::string> m_faceSheets;
  std::vector<std::string> m_folderDir;
  Directory m_faceDirectory;
  CheckerboardTexture m_checkerboardTexture;
  int m_selectedFolder{-1};
  int m_selectedSheet{-1};
  int m_selectionX{0};
  int m_selectionY{0};
  int m_faceIndex{0};
};


code:
#include "Core/CommonUI/GameWindowColorPicker.hpp"

#include "GroupBox.hpp"
#include "orei18n.hpp"

#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "imgui_internal.h"

#include <format>

void GameWindowColorPicker::draw(ImVec2 size) {

  ImGui::BeginHorizontal("##window_color_horz_layout", size);
  {
    ImGui::BeginVertical("##window_col_sliders", {size.x * .6f, 0});
    {
      GroupBox operationRedGroupBox(trNOOP("Red"), "##window_col_red_group", {size.x * .6f, 0}, nullptr, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize);
      if (operationRedGroupBox.begin()) {

        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x * .8f);
        if (ImGui::SliderInt("##window_color_red_slider", &m_r, -255, 255)) {
          m_backgroundInvalid = m_modified = true;
        }
        ImGui::SameLine();
        ImGui::SetNextItemWidth(-1);
        if (ImGui::SpinInt("##window_color_red_spinbox", &m_r, 1, 100)) {
          m_r = std::clamp(m_r, -255, 255);
          m_backgroundInvalid = m_modified = true;
        }
      }
      operationRedGroupBox.end();
      ImGui::Spring(.50f);
      GroupBox operationGreenGroupBox(trNOOP("Green"), "##change_window_green_group", {size.x * .6f, 0}, nullptr,
                                      ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize);

      if (operationGreenGroupBox.begin()) {

        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x * .8f);
        if (ImGui::SliderInt("##window_color_green_slider", &m_g, -255, 255)) {
          m_backgroundInvalid = m_modified = true;
        }
        ImGui::SameLine();
        ImGui::SetNextItemWidth(-1);
        if (ImGui::SpinInt("##window_color_green_spinbox", &m_g, 1, 100)) {
          m_g = std::clamp(m_g, -255, 255);
          m_backgroundInvalid = m_modified = true;
        }
      }
      operationGreenGroupBox.end();
      ImGui::Spring(.50f);
      GroupBox operationBlueGroupBox(trNOOP("Blue"), "##change_window_blue_layout", {size.x * .6f, 0}, nullptr,
                                     ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize);

      if (operationBlueGroupBox.begin()) {

        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x * .8f);
        if (ImGui::SliderInt("##window_color_blue_slider", &m_b, -255, 255)) {
          m_backgroundInvalid = m_modified = true;
        }
        ImGui::SameLine();
        ImGui::SetNextItemWidth(-1);
        if (ImGui::SpinInt("##window_color_blue_spinbox", &m_b, 1, 100)) {
          m_b = std::clamp(m_b, -255, 255);
          m_backgroundInvalid = m_modified = true;
        }
      }
      operationBlueGroupBox.end();
    }
    ImGui::EndVertical();
    ImGui::BeginChild(ImGui::GetID("##window_color_preview"), {size.x * .4f, size.y},
                      ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle);
    {
      if (m_backgroundInvalid) {
        m_background.update(m_r, m_g, m_b);
        m_backgroundInvalid = false;
      }
      ImGui::Image(m_background, ImGui::GetContentRegionAvail());
    }
    ImGui::EndChildFrame();
  }
  ImGui::EndHorizontal();
}

void GameWindowColorPicker::setPreviewSize(int width, int height) {
  if (width == m_background.width() && height == m_background.height()) {
    return;
  }
  m_background.setSize(width, height);
}


code:
#pragma once
#include "Core/Graphics/GameWindowBackground.hpp"

/**
 * @name GameWindowColorPicker
 * Provides a common UI tool to manipulate game window color values
 */
class GameWindowColorPicker {
public:
  /**
   * Sets the shift color channel values affecting the window color
   * @param r Red channel shift value, [-255,255] range
   * @param g Green channel shift value, [-255,255] range
   * @param b Blue channel shift value, [-255,255] range
   */
  void setValues(const int r, const int g, const int b) {
    m_r = r;
    m_g = g;
    m_b = b;
    m_modified = false;
    m_backgroundInvalid = true;
  }

  /**
   * Sets the base size of the preview image.
   * The values are scaled based on UI scale and applied automatically
   * @param width Unscaled width of the preview image
   * @param height Unscaled height of the preview image
   */
  void setPreviewSize(int width, int height);
  [[nodiscard]] int r() const { return m_r; }
  [[nodiscard]] int g() const { return m_g; }
  [[nodiscard]] int b() const { return m_b; }

  /**
   * Draws the GameWindowColorPicker elements.
   * @return True if any of the values were modified, false otherwise
   */
  void draw(ImVec2 size);

private:
  GameWindowBackground m_background;
  int m_r{0};
  int m_g{0};
  int m_b{0};
  int m_modified{false};
  int m_backgroundInvalid{true};
};


code:
//
// Created by antidote on 11/12/24.
//

#include "GridAtlasPicker.hpp"


code:
#pragma once

#include <string_view>

class GridAtlasPicker {
public:
  GridAtlasPicker() = delete;

private:
};