#include "timeline.hpp"

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <format>
#include <map>
#include <set>

#include <imgui_internal.h>

#include "actions.hpp"
#include "log.hpp"
#include "toast.hpp"
#include "util/imgui/draw.hpp"
#include "util/imgui/input.hpp"
#include "util/imgui/layout.hpp"
#include "util/imgui/shortcut.hpp"
#include "util/imgui/tooltip.hpp"

#include "vector.hpp"

using namespace anm2ed::resource;
using namespace anm2ed::types;
using namespace anm2ed::util;
using namespace glm;

namespace anm2ed::imgui
{

  constexpr auto WIDTH_MULTIPLIER = 1.25f;

  constexpr auto COLOR_HIDDEN_MULTIPLIER = vec4(0.5f, 0.5f, 0.5f, 1.000f);
  constexpr auto FRAME_BORDER_COLOR_DARK = ImVec4(1.0f, 1.0f, 1.0f, 0.15f);
  constexpr auto FRAME_BORDER_COLOR_LIGHT = ImVec4(0.0f, 0.0f, 0.0f, 0.25f);
  constexpr auto FRAME_BORDER_COLOR_REFERENCED_DARK = ImVec4(1.0f, 1.0f, 1.0f, 0.50f);
  constexpr auto FRAME_BORDER_COLOR_REFERENCED_LIGHT = ImVec4(0.0f, 0.0f, 0.0f, 0.60f);
  constexpr auto FRAME_BORDER_THICKNESS = 1.0f;
  constexpr auto FRAME_BORDER_THICKNESS_REFERENCED = 2.0f;
  constexpr auto FRAME_MULTIPLE_OVERLAY_COLOR_DARK = ImVec4(1.0f, 1.0f, 1.0f, 0.05f);
  constexpr auto FRAME_MULTIPLE_OVERLAY_COLOR_LIGHT = ImVec4(0.0f, 0.0f, 0.0f, 0.1f);
  constexpr auto FRAME_ROUNDING = 3.0f;
  constexpr auto ICON_TINT_DEFAULT_DARK = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
  constexpr auto ICON_TINT_DEFAULT_LIGHT = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
  constexpr auto ITEM_TEXT_COLOR_DARK = to_imvec4(color::WHITE);
  constexpr auto ITEM_TEXT_COLOR_LIGHT = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
  constexpr auto PLAYHEAD_ICON_TINT_LIGHT = ImVec4(0.3922f, 0.7843f, 0.5882f, 1.0f);
  constexpr auto PLAYHEAD_LINE_COLOR_DARK = to_imvec4(color::WHITE);
  constexpr auto PLAYHEAD_LINE_COLOR_LIGHT = ImVec4(0.1961f, 0.5882f, 0.3922f, 1.0f);
  constexpr auto PLAYHEAD_LINE_THICKNESS = 4.0f;
  constexpr auto TEXT_MULTIPLE_COLOR_DARK = to_imvec4(color::WHITE);
  constexpr auto TEXT_MULTIPLE_COLOR_LIGHT = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
  constexpr auto TIMELINE_BACKGROUND_COLOR_LIGHT = ImVec4(0.5490f, 0.5490f, 0.5882f, 1.0f);
  constexpr auto TIMELINE_PLAYHEAD_RECT_COLOR_DARK = ImVec4(0.60f, 0.45f, 0.30f, 1.0f);
  constexpr auto TIMELINE_PLAYHEAD_RECT_COLOR_LIGHT = ImVec4(0.8353f, 0.8353f, 0.7294f, 1.0f);
  constexpr auto TIMELINE_TICK_COLOR_LIGHT = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
  constexpr auto TIMELINE_CHILD_BG_COLOR_LIGHT = ImVec4(0.5490f, 0.5490f, 0.5882f, 1.0f);
  constexpr auto TIMELINE_TEXT_COLOR_LIGHT = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);

  enum class DropZone
  {
    BEFORE,
    INSIDE,
    AFTER
  };

  constexpr glm::vec4 FRAME_COLOR_LIGHT_BASE[] = {{0.80f, 0.80f, 0.80f, 1.0f},
                                                  {0.5216f, 0.7333f, 1.0f, 1.0f},
                                                  {1.0f, 0.9961f, 0.5882f, 1.0f},
                                                  {0.6157f, 1.0f, 0.5882f, 1.0f},
                                                  {1.0f, 0.5882f, 0.8314f, 1.0f}};
  constexpr glm::vec4 FRAME_COLOR_LIGHT_ACTIVE[] = {{0.74f, 0.74f, 0.74f, 1.0f},
                                                    {0.0980f, 0.3765f, 0.6431f, 1.0f},
                                                    {1.0f, 0.5255f, 0.3333f, 1.0f},
                                                    {0.3686f, 0.5765f, 0.2353f, 1.0f},
                                                    {0.6118f, 0.2039f, 0.2745f, 1.0f}};
  constexpr glm::vec4 FRAME_COLOR_LIGHT_HOVERED[] = {{0.84f, 0.84f, 0.84f, 1.0f},
                                                     {0.0752f, 0.2887f, 0.4931f, 1.0f},
                                                     {0.85f, 0.4467f, 0.2833f, 1.0f},
                                                     {0.2727f, 0.4265f, 0.1741f, 1.0f},
                                                     {0.4618f, 0.1539f, 0.2072f, 1.0f}};
  constexpr glm::vec4 ITEM_COLOR_LIGHT_BASE[] = {{0.3059f, 0.3255f, 0.5412f, 1.0f},
                                                 {0.3333f, 0.5725f, 0.8392f, 1.0f},
                                                 {1.0f, 0.5412f, 0.3412f, 1.0f},
                                                 {0.5255f, 0.8471f, 0.4588f, 1.0f},
                                                 {0.7961f, 0.3882f, 0.5412f, 1.0f}};
  constexpr glm::vec4 ITEM_COLOR_LIGHT_ACTIVE[] = {{0.3459f, 0.3655f, 0.5812f, 1.0f},
                                                   {0.3733f, 0.6125f, 0.8792f, 1.0f},
                                                   {0.9106f, 0.4949f, 0.2753f, 1.0f},
                                                   {0.5655f, 0.8871f, 0.4988f, 1.0f},
                                                   {0.8361f, 0.4282f, 0.5812f, 1.0f}};
  constexpr glm::vec4 ITEM_COLOR_LIGHT_SELECTED[] = {{0.74f, 0.74f, 0.74f, 1.0f},
                                                     {0.2039f, 0.4549f, 0.7176f, 1.0f},
                                                     {0.8745f, 0.4392f, 0.2275f, 1.0f},
                                                     {0.3765f, 0.6784f, 0.2980f, 1.0f},
                                                     {0.6353f, 0.2235f, 0.3647f, 1.0f}};

  constexpr auto FRAME_MULTIPLE = 5;
  constexpr auto FRAME_TOOLTIP_HOVER_DELAY = 0.75f;

#define ITEM_CHILD_WIDTH ImGui::GetTextLineHeightWithSpacing() * 12.5

  struct TimelineItemRow
  {
    int type{NONE};
    int id{-1};
    int index{-1};
    int groupId{-1};
    int depth{};
    bool isGroup{};
  };

  void Timeline::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
  {
    auto& document = *manager.get();
    auto& anm2 = document.anm2;
    auto& playback = document.playback;
    auto& reference = document.reference;
    auto& frames = document.frames;
    auto& region = document.region;
    auto animation = anm2.element_get(ElementType::ANIMATION, reference.animationIndex);
    auto rowFrameChildHeight = ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y * 1.5f;

    style = ImGui::GetStyle();
    auto isLightTheme = settings.theme == theme::LIGHT;
    bool isTextPushed = false;
    if (isLightTheme)
    {
      ImGui::PushStyleColor(ImGuiCol_Text, TIMELINE_TEXT_COLOR_LIGHT);
      isTextPushed = true;
    }

    auto type_index = [](int type) { return std::clamp(type, 0, (int)TRIGGER); };
    auto item_type_get = [](int type) { return static_cast<ItemType>(type); };
    auto item_get = [&](int type, int id = -1)
    { return animation ? animation_item_get(*animation, item_type_get(type), id) : nullptr; };
    auto frame_get = [&]()
    {
      return anm2.element_get(reference.animationIndex, item_type_get(reference.itemType), reference.frameIndex,
                              reference.itemID);
    };
    auto selected_item_get = [&]()
    { return anm2.element_get(reference.animationIndex, item_type_get(reference.itemType), reference.itemID); };
    auto item_frames_count = [](const Element* item) { return item ? (int)item->children.size() : 0; };
    auto layer_get = [&](int id) { return anm2.element_get(ElementType::LAYER_ELEMENT, id); };
    auto null_get = [&](int id)
    {
      auto nulls = anm2.element_get(ElementType::NULLS);
      return nulls ? element_child_id_get(*nulls, ElementType::NULL_ELEMENT, id) : nullptr;
    };
    auto spritesheet_get = [&](int id) { return anm2.element_get(ElementType::SPRITESHEET, id); };
    auto info_get = [&]() { return element_first_get(anm2.root, ElementType::INFO); };
    auto container_type_get = [](int type)
    {
      if (type == LAYER) return ElementType::LAYER_ANIMATIONS;
      if (type == NULL_) return ElementType::NULL_ANIMATIONS;
      return ElementType::UNKNOWN;
    };
    auto track_type_get = [](int type)
    {
      if (type == LAYER) return ElementType::LAYER_ANIMATION;
      if (type == NULL_) return ElementType::NULL_ANIMATION;
      return ElementType::UNKNOWN;
    };
    auto track_id_get = [](const Element& track, int type)
    {
      if (type == LAYER) return track.layerId;
      if (type == NULL_) return track.nullId;
      return -1;
    };
    auto track_container_get = [&](int type)
    {
      return animation ? element_child_first_get(*animation, container_type_get(type)) : nullptr;
    };
    auto track_group_get = [&](int type, int groupId)
    {
      auto container = track_container_get(type);
      return container ? element_child_id_get(*container, ElementType::GROUP, groupId) : nullptr;
    };
    auto is_track_group_visible = [&](int type, int groupId)
    {
      if (groupId == -1) return true;
      auto group = track_group_get(type, groupId);
      return !group || group->isVisible;
    };
    auto row_group_get = [&](const TimelineItemRow& row) -> Element*
    {
      return row.isGroup ? track_group_get(row.type, row.id) : nullptr;
    };
    auto group_items_count_get = [&](int type, int groupId)
    {
      auto container = track_container_get(type);
      auto trackType = track_type_get(type);
      int count{};
      if (!container) return count;
      for (auto& item : container->children)
        if (item.type == trackType && item.groupId == groupId) ++count;
      return count;
    };

    auto command_animation_get = [](Document& document, int animationIndex)
    { return document.anm2.element_get(ElementType::ANIMATION, animationIndex); };
    auto command_item_get = [](Document& document, int animationIndex, int type, int id)
    {
      auto animation = document.anm2.element_get(ElementType::ANIMATION, animationIndex);
      return animation ? animation_item_get(*animation, static_cast<ItemType>(type), id) : nullptr;
    };
    auto command_frame_get = [](Document& document, const Reference& targetReference)
    {
      return document.anm2.element_get(targetReference.animationIndex, static_cast<ItemType>(targetReference.itemType),
                                       targetReference.frameIndex, targetReference.itemID);
    };
    auto command_layer_get = [](Document& document, int id)
    { return document.anm2.element_get(ElementType::LAYER_ELEMENT, id); };
    auto command_spritesheet_get = [](Document& document, int id)
    { return document.anm2.element_get(ElementType::SPRITESHEET, id); };
    auto command_info_get = [](Document& document)
    { return element_first_get(document.anm2.root, ElementType::INFO); };
    auto item_reference_get = [&](int type, int id) { return Reference{reference.animationIndex, type, id}; };
    auto item_reference_from_frame_get = [](Reference frameReference)
    {
      frameReference.frameIndex = -1;
      return frameReference;
    };
    auto is_same_item = [](const Reference& left, const Reference& right)
    {
      return left.animationIndex == right.animationIndex && left.itemType == right.itemType &&
             left.itemID == right.itemID;
    };
    auto group_selection_reset_for = [this](Document& targetDocument)
    {
      targetDocument.groupReferences.clear();
      isRowSelectionAnchorSet = false;
    };
    auto frame_references_for_current_get = [&]()
    {
      std::set<Reference> result = frames.references;
      if (result.empty())
        for (auto frameIndex : frames.selection)
          result.insert({reference.animationIndex, reference.itemType, reference.itemID, frameIndex});
      return result;
    };
    auto item_references_for_current_get = [&]()
    {
      std::set<Reference> result = document.items.references;
      if (result.empty() && reference.itemType != NONE)
        result.insert(item_reference_get(reference.itemType, reference.itemID));
      return result;
    };
    auto frames_selection_sync_for = [&](Document& targetDocument)
    {
      targetDocument.frames.selection.clear();
      for (const auto& frameReference : targetDocument.frames.references)
        if (is_same_item(frameReference, targetDocument.reference) && frameReference.frameIndex >= 0)
          targetDocument.frames.selection.insert(frameReference.frameIndex);
      frameSelectionSnapshot.assign(targetDocument.frames.selection.begin(), targetDocument.frames.selection.end());
      frameSelectionSnapshotReference = targetDocument.reference;
    };
    auto item_selection_set_for = [&](Document& targetDocument, Reference itemReference)
    {
      itemReference.frameIndex = -1;
      targetDocument.items.references = {itemReference};
    };
    auto frame_selection_set_for = [&](Document& targetDocument, Reference frameReference)
    {
      group_selection_reset_for(targetDocument);
      targetDocument.frames.references = {frameReference};
      targetDocument.reference = frameReference;
      item_selection_set_for(targetDocument, item_reference_from_frame_get(frameReference));
      frames_selection_sync_for(targetDocument);
    };
    auto frame_selection_toggle_for = [&](Document& targetDocument, Reference frameReference)
    {
      group_selection_reset_for(targetDocument);
      auto& selection = targetDocument.frames.references;
      auto itemReference = item_reference_from_frame_get(frameReference);
      if (selection.contains(frameReference))
      {
        if (selection.size() > 1) selection.erase(frameReference);
        bool isItemStillSelected{};
        for (const auto& selectedFrame : selection)
          if (is_same_item(selectedFrame, itemReference)) isItemStillSelected = true;
        if (!isItemStillSelected && targetDocument.items.references.size() > 1)
          targetDocument.items.references.erase(itemReference);
      }
      else
      {
        selection.insert(frameReference);
        targetDocument.items.references.insert(itemReference);
      }
      targetDocument.reference = frameReference;
      frames_selection_sync_for(targetDocument);
    };
    auto frame_selection_range_set_for = [&](Document& targetDocument, Reference firstReference,
                                             Reference lastReference, bool isAdditive) -> bool
    {
      group_selection_reset_for(targetDocument);
      if (!is_same_item(firstReference, lastReference) || firstReference.frameIndex < 0 || lastReference.frameIndex < 0)
        return false;

      auto item = command_item_get(targetDocument, lastReference.animationIndex, lastReference.itemType,
                                   lastReference.itemID);
      if (!item) return false;
      if (firstReference.frameIndex >= (int)item->children.size() ||
          lastReference.frameIndex >= (int)item->children.size())
        return false;

      auto firstIndex = firstReference.frameIndex;
      auto lastIndex = lastReference.frameIndex;
      if (firstIndex > lastIndex) std::swap(firstIndex, lastIndex);
      auto itemReference = item_reference_from_frame_get(lastReference);
      if (!isAdditive)
      {
        targetDocument.frames.references.clear();
        item_selection_set_for(targetDocument, itemReference);
      }
      else
        targetDocument.items.references.insert(itemReference);

      for (int i = firstIndex; i <= lastIndex; ++i)
        targetDocument.frames.references.insert(
            {lastReference.animationIndex, lastReference.itemType, lastReference.itemID, i});

      targetDocument.reference = lastReference;
      frames_selection_sync_for(targetDocument);
      return true;
    };
    auto all_frame_references_for_item_get = [&](const Reference& itemReference)
    {
      std::set<Reference> result{};
      auto item = item_get(itemReference.itemType, itemReference.itemID);
      if (!item) return result;
      for (int i = 0; i < (int)item->children.size(); ++i)
        result.insert({itemReference.animationIndex, itemReference.itemType, itemReference.itemID, i});
      return result;
    };
    auto all_frame_references_for_items_get = [&]()
    {
      std::set<Reference> result{};
      auto itemReferences = item_references_for_current_get();
      if (itemReferences.empty())
        itemReferences.insert(item_reference_get(reference.itemType, reference.itemID));
      for (auto itemReference : itemReferences)
      {
        auto itemFrames = all_frame_references_for_item_get(itemReference);
        result.insert(itemFrames.begin(), itemFrames.end());
      }
      return result;
    };
    auto frames_selection_reset_for = [this](Document& targetDocument)
    {
      targetDocument.frames.selection.clear();
      targetDocument.frames.references.clear();
      frameSelectionSnapshot.clear();
      frameSelectionLocked.clear();
      isFrameSelectionLocked = false;
      frameFocusRequested = false;
      frameFocusIndex = -1;
      frameSelectionSnapshotReference = targetDocument.reference;
    };
    auto frames_selection_set_reference_for = [this](Document& targetDocument)
    {
      auto& targetFrames = targetDocument.frames;
      auto& targetReference = targetDocument.reference;
      targetFrames.selection.clear();
      targetFrames.references.clear();
      if (targetReference.frameIndex >= 0)
      {
        targetFrames.selection.insert(targetReference.frameIndex);
        targetFrames.references.insert(targetReference);
      }
      frameSelectionSnapshot.assign(targetFrames.selection.begin(), targetFrames.selection.end());
      frameSelectionSnapshotReference = targetReference;
      frameSelectionLocked.clear();
      isFrameSelectionLocked = false;
      frameFocusIndex = targetReference.frameIndex;
      frameFocusRequested = targetReference.frameIndex >= 0;
    };
    auto reference_clear_for = [=](Document& targetDocument)
    {
      targetDocument.reference = {targetDocument.reference.animationIndex};
      frames_selection_reset_for(targetDocument);
      targetDocument.items.references.clear();
    };
    auto reference_set_item_for = [=](Document& targetDocument, int type, int id)
    {
      targetDocument.reference = {targetDocument.reference.animationIndex, type, id};
      frames_selection_reset_for(targetDocument);
      item_selection_set_for(targetDocument, targetDocument.reference);
    };
    auto reference_set_timeline_item_for = [=](Document& targetDocument, int type, int id)
    {
      if (type == LAYER)
        if (auto layer = command_layer_get(targetDocument, id))
          targetDocument.spritesheet.reference = layer->spritesheetId;
      reference_set_item_for(targetDocument, type, id);
    };
    auto command_push = [&](auto run)
    {
      manager.command_push({manager.selected,
                            [run](Manager& manager, Document& document) mutable { run(manager, document); }});
    };
    auto snapshot_command_push = [&](StringType messageType)
    {
      auto message = std::string(localize.get(messageType));
      manager.command_push(
          {manager.selected, [message](Manager&, Document& document) { document.snapshot(message); }});
    };
    auto edit_command_push = [&](StringType messageType, Document::ChangeType changeType, auto run)
    {
      auto message = std::string(localize.get(messageType));
      manager.command_push({manager.selected,
                            [=](Manager& manager, Document& document) mutable
                            {
                              document.snapshot(message);
                              run(manager, document);
                              document.anm2_change(changeType);
                            }});
    };
    auto type_color_base_vec = [&](int type)
    { return isLightTheme ? FRAME_COLOR_LIGHT_BASE[type_index(type)] : TYPE_COLOR[type]; };

    auto type_color_active_vec = [&](int type)
    {
      if (isLightTheme) return FRAME_COLOR_LIGHT_ACTIVE[type_index(type)];
      return TYPE_COLOR_ACTIVE[type];
    };

    auto type_color_hovered_vec = [&](int type)
    {
      if (isLightTheme) return FRAME_COLOR_LIGHT_HOVERED[type_index(type)];
      return TYPE_COLOR_HOVERED[type];
    };

    auto item_color_vec = [&](int type)
    {
      if (!isLightTheme) return TYPE_COLOR[type];
      return ITEM_COLOR_LIGHT_BASE[type_index(type)];
    };

    auto item_color_active_vec = [&](int type)
    {
      if (!isLightTheme) return TYPE_COLOR_ACTIVE[type];
      return ITEM_COLOR_LIGHT_ACTIVE[type_index(type)];
    };

    auto iconTintDefault = isLightTheme ? ICON_TINT_DEFAULT_LIGHT : ICON_TINT_DEFAULT_DARK;
    auto itemIconTint = isLightTheme ? ICON_TINT_DEFAULT_LIGHT : iconTintDefault;
    auto frameBorderColor = isLightTheme ? FRAME_BORDER_COLOR_LIGHT : FRAME_BORDER_COLOR_DARK;
    auto frameBorderColorReferenced =
        isLightTheme ? FRAME_BORDER_COLOR_REFERENCED_LIGHT : FRAME_BORDER_COLOR_REFERENCED_DARK;
    auto frameMultipleOverlayColor =
        isLightTheme ? FRAME_MULTIPLE_OVERLAY_COLOR_LIGHT : FRAME_MULTIPLE_OVERLAY_COLOR_DARK;
    auto textMultipleColor = isLightTheme ? TEXT_MULTIPLE_COLOR_LIGHT : TEXT_MULTIPLE_COLOR_DARK;
    auto playheadLineColor = isLightTheme ? PLAYHEAD_LINE_COLOR_LIGHT : PLAYHEAD_LINE_COLOR_DARK;
    auto playheadIconTint = isLightTheme ? PLAYHEAD_ICON_TINT_LIGHT : iconTintDefault;
    auto timelineBackgroundColor =
        isLightTheme ? TIMELINE_BACKGROUND_COLOR_LIGHT : ImGui::GetStyleColorVec4(ImGuiCol_Header);
    auto timelinePlayheadRectColor =
        isLightTheme ? TIMELINE_PLAYHEAD_RECT_COLOR_LIGHT : TIMELINE_PLAYHEAD_RECT_COLOR_DARK;
    auto timelineTickColor = isLightTheme ? TIMELINE_TICK_COLOR_LIGHT : frameBorderColor;
    auto itemTextColor = isLightTheme ? ITEM_TEXT_COLOR_LIGHT : ITEM_TEXT_COLOR_DARK;

    auto overlay_icon = [&](GLuint textureId, ImVec4 tint, bool isForced = false)
    {
      if (!isForced && !isLightTheme) return;
      auto min = ImGui::GetItemRectMin();
      auto max = ImGui::GetItemRectMax();
      ImGui::GetWindowDrawList()->AddImage((ImTextureID)(intptr_t)textureId, min, max, ImVec2(0, 0), ImVec2(1, 1),
                                           ImGui::GetColorU32(tint));
    };

    auto frames_selection_set_reference = [&]()
    {
      frames_selection_set_reference_for(document);
    };

    auto playback_stop = [&]()
    {
      playback.isPlaying = false;
      playback.isFinished = false;
      playback.timing_reset();
    };

    auto frame_insert = [&]()
    {
      auto targetReference = reference;
      auto targetFrameTime = document.frameTime;
      if (!animation || !command_item_get(document, targetReference.animationIndex, targetReference.itemType,
                                          targetReference.itemID))
        return;

      edit_command_push(EDIT_INSERT_FRAME, Document::FRAMES,
                        [=, this](Manager&, Document& document) mutable
                        {
                          auto animation = command_animation_get(document, targetReference.animationIndex);
                          auto item = command_item_get(document, targetReference.animationIndex,
                                                       targetReference.itemType, targetReference.itemID);
                          if (!animation || !item) return;

                          auto newReference = targetReference;

                          if (targetReference.itemType == TRIGGER)
                          {
                            for (auto& trigger : item->children)
                              if (targetFrameTime == trigger.atFrame) return;

                            auto addFrame = element_make(ElementType::TRIGGER);
                            addFrame.atFrame = targetFrameTime;
                            item->children.push_back(addFrame);
                            frames_sort_by_at_frame(*item);
                            newReference.frameIndex = frame_index_from_at_frame_get(*item, addFrame.atFrame);
                          }
                          else
                          {
                            auto frame = command_frame_get(document, targetReference);
                            if (frame)
                            {
                              auto addFrame = *frame;
                              auto insertIndex =
                                  std::clamp(targetReference.frameIndex + 1, 0, (int)item->children.size());
                              item->children.insert(item->children.begin() + insertIndex, addFrame);
                              newReference.frameIndex = insertIndex;
                            }
                            else if (!item->children.empty())
                            {
                              auto addFrame = item->children.back();
                              item->children.emplace_back(addFrame);
                              newReference.frameIndex = (int)item->children.size() - 1;
                            }
                            else
                            {
                              item->children.emplace_back(element_make(ElementType::FRAME));
                              newReference.frameIndex = 0;
                            }
                          }

                          document.reference = newReference;
                          frames_selection_set_reference_for(document);
                          if (newReference.itemType != TRIGGER)
                            document.frameTime = frame_time_from_index_get(*item, newReference.frameIndex);
                        });
    };

    auto frames_delete_for = [=](Document& document, std::set<Reference> selectedFrames) mutable
    {
      std::map<Reference, std::set<int>> groupedFrames{};
      for (auto frameReference : selectedFrames)
      {
        auto itemReference = item_reference_from_frame_get(frameReference);
        groupedFrames[itemReference].insert(frameReference.frameIndex);
      }

      for (auto& [itemReference, indices] : groupedFrames)
      {
        auto item = command_item_get(document, itemReference.animationIndex, itemReference.itemType,
                                     itemReference.itemID);
        if (!item) continue;

        for (auto it = indices.rbegin(); it != indices.rend(); ++it)
        {
          auto i = *it;
          if (i >= 0 && i < (int)item->children.size()) item->children.erase(item->children.begin() + i);
        }
      }

      auto item = command_item_get(document, document.reference.animationIndex, document.reference.itemType,
                                   document.reference.itemID);
      if (!item || item->children.empty())
      {
        document.reference.frameIndex = -1;
        frames_selection_reset_for(document);
        return;
      }

      document.reference.frameIndex = glm::clamp(document.reference.frameIndex, 0, (int)item->children.size() - 1);
      frames_selection_set_reference_for(document);
    };

    auto frames_delete_action = [&]()
    {
      auto selectedFrames = frame_references_for_current_get();
      if (selectedFrames.empty()) return;
      edit_command_push(EDIT_DELETE_FRAMES, Document::FRAMES,
                        [=](Manager&, Document& document) mutable
                        { frames_delete_for(document, selectedFrames); });
    };

    auto frames_bake = [&]()
    {
      auto selectedFrames = frame_references_for_current_get();
      auto bakeInterval = settings.bakeInterval;
      auto isRoundScale = settings.bakeIsRoundScale;
      auto isRoundRotation = settings.bakeIsRoundRotation;
      std::erase_if(selectedFrames,
                    [](const Reference& frameReference) { return frameReference.itemType == TRIGGER; });
      if (selectedFrames.empty()) return;

      edit_command_push(EDIT_BAKE_FRAMES, Document::FRAMES,
                        [=, this](Manager&, Document& document) mutable
                        {
                          std::map<Reference, std::set<int>> groupedFrames{};
                          for (auto frameReference : selectedFrames)
                          {
                            auto itemReference = item_reference_from_frame_get(frameReference);
                            groupedFrames[itemReference].insert(frameReference.frameIndex);
                          }

                          std::set<Reference> bakedSelection{};
                          for (auto& [itemReference, indices] : groupedFrames)
                          {
                            auto item = command_item_get(document, itemReference.animationIndex,
                                                         itemReference.itemType, itemReference.itemID);
                            if (!item) continue;

                            int insertedBefore = 0;
                            for (auto originalIndex : indices)
                            {
                              auto i = originalIndex + insertedBefore;
                              if (!vector::in_bounds(item->children, i)) continue;

                              auto originalDuration = item->children[i].duration;
                              frame_bake(*item, i, bakeInterval, isRoundScale, isRoundRotation);

                              auto bakedCount = originalDuration <= FRAME_DURATION_MIN
                                                    ? 1
                                                    : (int)std::ceil((float)originalDuration / bakeInterval);
                              for (int offset = 0; offset < bakedCount; ++offset)
                                bakedSelection.insert(
                                    {itemReference.animationIndex, itemReference.itemType, itemReference.itemID,
                                     i + offset});

                              insertedBefore += bakedCount - 1;
                            }
                          }

                          document.frames.references = std::move(bakedSelection);
                          if (!document.frames.references.empty())
                          {
                            document.reference = *document.frames.references.begin();
                            frames_selection_sync_for(document);
                            frameSelectionSnapshotReference = document.reference;
                            frameSelectionLocked.clear();
                            isFrameSelectionLocked = false;
                            frameFocusIndex = document.reference.frameIndex;
                            frameFocusRequested = true;
                          }
                          else
                            frames_selection_reset_for(document);
                        });
    };

    auto frame_split = [&]()
    {
      auto selectedFrames = frame_references_for_current_get();
      if (selectedFrames.size() != 1) return;
      auto targetReference = *selectedFrames.begin();
      auto playheadTime = (int)std::floor(playback.time);
      if (targetReference.itemType == TRIGGER) return;

      edit_command_push(EDIT_SPLIT_FRAME, Document::FRAMES,
                        [=](Manager&, Document& document) mutable
                        {
                          if (targetReference.itemType == TRIGGER) return;

                          auto item = command_item_get(document, targetReference.animationIndex,
                                                       targetReference.itemType, targetReference.itemID);
                          auto frame = command_frame_get(document, targetReference);

                          if (!item || !frame) return;

                          auto originalDuration = frame->duration;
                          if (originalDuration <= 1) return;

                          auto frameStartTime = frame_time_from_index_get(*item, targetReference.frameIndex);
                          int frameStart = (int)std::round(frameStartTime);
                          int firstDuration = playheadTime - frameStart + 1;

                          if (firstDuration <= 0 || firstDuration >= originalDuration) return;

                          int secondDuration = originalDuration - firstDuration;
                          auto splitFrame = *frame;
                          splitFrame.duration = secondDuration;

                          auto nextFrame = track_frame_get(*item, targetReference.frameIndex + 1);
                          if (frame->interpolation != Interpolation::NONE && nextFrame)
                          {
                            float interpolation = (float)firstDuration / (float)originalDuration;
                            switch (frame->interpolation)
                            {
                              case Interpolation::EASE_IN:
                                interpolation *= interpolation;
                                break;
                              case Interpolation::EASE_OUT:
                                interpolation = 1.0f - ((1.0f - interpolation) * (1.0f - interpolation));
                                break;
                              case Interpolation::EASE_IN_OUT:
                                interpolation = interpolation < 0.5f
                                                    ? (2.0f * interpolation * interpolation)
                                                    : (1.0f - std::pow(-2.0f * interpolation + 2.0f, 2.0f) * 0.5f);
                                break;
                              case Interpolation::LINEAR:
                              case Interpolation::NONE:
                              default:
                                break;
                            }

                            splitFrame.rotation = glm::mix(frame->rotation, nextFrame->rotation, interpolation);
                            splitFrame.position = glm::mix(frame->position, nextFrame->position, interpolation);
                            splitFrame.scale = glm::mix(frame->scale, nextFrame->scale, interpolation);
                            splitFrame.colorOffset = glm::mix(frame->colorOffset, nextFrame->colorOffset, interpolation);
                            splitFrame.tint = glm::mix(frame->tint, nextFrame->tint, interpolation);
                          }

                          frame->duration = firstDuration;
                          item->children.insert(item->children.begin() + targetReference.frameIndex + 1, splitFrame);
                          document.reference = targetReference;
                          frames_selection_set_reference_for(document);
                        });
    };

    auto reference_clear = [&]()
    {
      group_selection_reset_for(document);
      reference_clear_for(document);
    };

    auto reference_set_timeline_item = [&](int type, int id)
    {
      group_selection_reset_for(document);
      reference_set_timeline_item_for(document, type, id);
    };

    auto timeline_item_rows_get = [&]()
    {
      std::vector<TimelineItemRow> rows{};
      if (!animation) return rows;

      auto track_row_push = [&](const Element& item, int type, int index, int depth = 0)
      {
        rows.push_back({.type = type,
                        .id = track_id_get(item, type),
                        .index = index,
                        .groupId = item.groupId,
                        .depth = depth});
      };
      auto group_row_push = [&](const Element& group, int type, int index)
      {
        rows.push_back({.type = type, .id = group.id, .index = index, .isGroup = true});
      };
      auto group_ids_get = [](const Element& container)
      {
        std::set<int> result{};
        for (const auto& item : container.children)
          if (item.type == ElementType::GROUP) result.insert(item.id);
        return result;
      };

      rows.push_back({.type = ROOT});

      if (auto layerAnimations = element_child_first_get(*animation, ElementType::LAYER_ANIMATIONS))
        {
          auto groupIds = group_ids_get(*layerAnimations);
          auto layer_track_push = [&](int groupId, int depth)
          {
            for (int j = (int)layerAnimations->children.size() - 1; j >= 0; --j)
            {
              auto& item = layerAnimations->children[j];
              if (item.type != ElementType::LAYER_ANIMATION || item.groupId != groupId) continue;
              if (settings.timelineIsShowUnused || !item.children.empty()) track_row_push(item, LAYER, j, depth);
            }
          };

          for (int i = (int)layerAnimations->children.size() - 1; i >= 0; --i)
          {
            auto& item = layerAnimations->children[i];
            if (item.type == ElementType::GROUP)
            {
              group_row_push(item, LAYER, i);
              if (item.isExpanded) layer_track_push(item.id, 1);
            }
            else if (item.type == ElementType::LAYER_ANIMATION && !groupIds.contains(item.groupId) &&
                     (settings.timelineIsShowUnused || !item.children.empty()))
              track_row_push(item, LAYER, i);
          }
        }

      if (auto nullAnimations = element_child_first_get(*animation, ElementType::NULL_ANIMATIONS))
        {
          auto groupIds = group_ids_get(*nullAnimations);
          auto null_track_push = [&](int groupId, int depth)
          {
            for (int j = 0; j < (int)nullAnimations->children.size(); ++j)
            {
              auto& item = nullAnimations->children[j];
              if (item.type != ElementType::NULL_ANIMATION || item.groupId != groupId) continue;
              if (settings.timelineIsShowUnused || !item.children.empty()) track_row_push(item, NULL_, j, depth);
            }
          };

          for (int i = 0; i < (int)nullAnimations->children.size(); ++i)
          {
            auto& item = nullAnimations->children[i];
            if (item.type == ElementType::GROUP)
            {
              group_row_push(item, NULL_, i);
              if (item.isExpanded) null_track_push(item.id, 1);
            }
            else if (item.type == ElementType::NULL_ANIMATION && !groupIds.contains(item.groupId) &&
                     (settings.timelineIsShowUnused || !item.children.empty()))
              track_row_push(item, NULL_, i);
          }
        }

      rows.push_back({.type = TRIGGER});
      return rows;
    };

    auto timeline_item_references_get = [&]()
    {
      std::vector<Reference> itemReferences;
      for (const auto& row : timeline_item_rows_get())
      {
        if (row.isGroup) continue;
        itemReferences.push_back({reference.animationIndex, row.type, row.id});
      }
      return itemReferences;
    };

    auto group_reference_get = [&](const TimelineItemRow& row)
    { return Reference{reference.animationIndex, row.type, row.id}; };

    auto row_reference_get = [&](const TimelineItemRow& row)
    {
      return TimelineRowReference{manager.selected, reference.animationIndex, row.type, row.id, row.index, row.isGroup};
    };

    auto row_item_reference_get = [](const TimelineRowReference& row)
    { return Reference{row.animationIndex, row.type, row.id}; };

    auto timeline_row_references_get = [&]()
    {
      std::vector<TimelineRowReference> rowReferences;
      for (const auto& row : timeline_item_rows_get())
      {
        if (row.type == NONE) continue;
        rowReferences.push_back(row_reference_get(row));
      }
      return rowReferences;
    };

    auto is_group_selected = [&](const TimelineItemRow& row)
    {
      return document.groupReferences.contains(group_reference_get(row));
    };

    auto is_row_selected = [&](const TimelineItemRow& row)
    {
      if (row.isGroup) return is_group_selected(row);
      auto itemReference = item_reference_get(row.type, row.id);
      auto isReferenced = reference.itemType == row.type && reference.itemID == row.id;
      return document.items.references.contains(itemReference) ||
             (document.items.references.empty() && document.groupReferences.empty() && isReferenced);
    };

    auto row_selection_clear = [&]()
    {
      document.items.references.clear();
      document.groupReferences.clear();
    };

    auto row_selection_insert = [&](const TimelineRowReference& row)
    {
      if (row.isGroup)
        document.groupReferences.insert({row.animationIndex, row.type, row.id});
      else
        document.items.references.insert(row_item_reference_get(row));
    };

    auto row_selection_erase = [&](const TimelineRowReference& row)
    {
      if (row.isGroup)
        document.groupReferences.erase({row.animationIndex, row.type, row.id});
      else
        document.items.references.erase(row_item_reference_get(row));
    };

    auto is_row_reference_selected = [&](const TimelineRowReference& row)
    {
      if (row.isGroup) return document.groupReferences.contains({row.animationIndex, row.type, row.id});
      return document.items.references.contains(row_item_reference_get(row));
    };

    auto row_selection_count_get = [&]() { return document.items.references.size() + document.groupReferences.size(); };

    auto row_selection_set = [&](const TimelineItemRow& row)
    {
      auto rowReference = row_reference_get(row);
      auto isCtrlDown = ImGui::IsKeyDown(ImGuiMod_Ctrl);
      auto isShiftDown = ImGui::IsKeyDown(ImGuiMod_Shift);

      if (row.isGroup)
        reference = {reference.animationIndex};
      else
      {
        if (row.type == LAYER)
          if (auto layer = layer_get(row.id)) document.spritesheet.reference = layer->spritesheetId;
        reference = row_item_reference_get(rowReference);
      }
      frames_selection_reset_for(document);

      if (isShiftDown)
      {
        auto rowSelection = timeline_row_references_get();
        auto anchor = isRowSelectionAnchorSet ? rowSelectionAnchor : rowReference;
        auto first = std::find(rowSelection.begin(), rowSelection.end(), anchor);
        auto last = std::find(rowSelection.begin(), rowSelection.end(), rowReference);
        if (first == rowSelection.end() || last == rowSelection.end())
        {
          if (!isCtrlDown) row_selection_clear();
          row_selection_insert(rowReference);
          rowSelectionAnchor = rowReference;
        }
        else
        {
          auto firstIndex = (int)std::distance(rowSelection.begin(), first);
          auto lastIndex = (int)std::distance(rowSelection.begin(), last);
          if (firstIndex > lastIndex) std::swap(firstIndex, lastIndex);
          if (!isCtrlDown) row_selection_clear();
          for (int i = firstIndex; i <= lastIndex; ++i)
            row_selection_insert(rowSelection[i]);
          if (!isRowSelectionAnchorSet) rowSelectionAnchor = rowReference;
        }
      }
      else if (isCtrlDown)
      {
        if (is_row_reference_selected(rowReference) && row_selection_count_get() > 1)
          row_selection_erase(rowReference);
        else
          row_selection_insert(rowReference);
        rowSelectionAnchor = rowReference;
      }
      else
      {
        row_selection_clear();
        row_selection_insert(rowReference);
        rowSelectionAnchor = rowReference;
      }

      isRowSelectionAnchorSet = true;
    };

    auto reference_set_adjacent_item = [&](int direction)
    {
      auto itemReferences = timeline_item_references_get();
      if (itemReferences.empty()) return;

      auto it = std::find_if(
          itemReferences.begin(), itemReferences.end(), [&](const Reference& itemReference)
          { return itemReference.itemType == reference.itemType && itemReference.itemID == reference.itemID; });

      int index = direction > 0 ? 0 : (int)itemReferences.size() - 1;
      if (it != itemReferences.end()) index = (int)std::distance(itemReferences.begin(), it) + direction;
      index = std::clamp(index, 0, (int)itemReferences.size() - 1);

      auto& itemReference = itemReferences[index];
      reference_set_timeline_item(itemReference.itemType, itemReference.itemID);
    };

    auto selected_row_references_get = [&]()
    {
      std::vector<TimelineRowReference> result{};
      for (const auto& row : timeline_item_rows_get())
        if (is_row_selected(row)) result.push_back(row_reference_get(row));
      return result;
    };

    auto row_drag_references_get = [&](const TimelineItemRow& row)
    {
      auto clicked = row_reference_get(row);
      if (clicked.type != LAYER && clicked.type != NULL_) return std::vector<TimelineRowReference>{clicked};

      auto rows = selected_row_references_get();
      if (!is_row_reference_selected(clicked)) rows = {clicked};
      if (rows.empty()) rows = {clicked};

      std::erase_if(rows, [&](const TimelineRowReference& rowReference) { return rowReference.type != clicked.type; });
      if (rows.empty()) rows = {clicked};
      return rows;
    };

    auto row_label_get = [&](const TimelineRowReference& row) -> std::string
    {
      if (row.isGroup)
      {
        TimelineItemRow groupRow{.type = row.type, .id = row.id, .index = row.index, .isGroup = true};
        auto group = row_group_get(groupRow);
        if (!group || group->name.empty()) return localize.get(TEXT_NEW_GROUP);
        return group->name;
      }
      if (row.type == LAYER)
      {
        auto layer = layer_get(row.id);
        if (!layer) return localize.get(TYPE_STRINGS[row.type]);
        return std::vformat(localize.get(FORMAT_LAYER),
                            std::make_format_args(layer->id, layer->name, layer->spritesheetId));
      }
      if (row.type == NULL_)
      {
        auto null = null_get(row.id);
        if (!null) return localize.get(TYPE_STRINGS[row.type]);
        return std::vformat(localize.get(FORMAT_NULL), std::make_format_args(null->id, null->name));
      }
      return localize.get(TYPE_STRINGS[type_index(row.type)]);
    };

    auto row_drag_tooltip_draw = [&](const std::vector<TimelineRowReference>& rows)
    {
      for (auto row : rows)
      {
        auto label = row_label_get(row);
        ImGui::TextUnformatted(label.c_str());
      }
    };

    auto item_remove = [&]()
    {
      auto targetRows = selected_row_references_get();
      std::map<int, std::set<int>> targetIds{};
      std::map<int, std::set<int>> targetGroupIds{};
      for (auto row : targetRows)
      {
        if (row.type != LAYER && row.type != NULL_) continue;
        if (row.isGroup)
          targetGroupIds[row.type].insert(row.id);
        else
          targetIds[row.type].insert(row.id);
      }
      auto animationIndex = reference.animationIndex;
      if (!animation) return;
      if (targetIds.empty() && targetGroupIds.empty()) return;
      edit_command_push(EDIT_REMOVE_ITEMS, Document::ITEMS,
                        [=, this](Manager&, Document& document) mutable
                        {
                          auto animation = command_animation_get(document, animationIndex);
                          if (!animation) return;
                          for (auto targetType : {LAYER, NULL_})
                          {
                            auto containerType = container_type_get(targetType);
                            auto targetTrackType = track_type_get(targetType);
                            auto container = element_child_first_get(*animation, containerType);
                            if (!container) continue;
                            auto ids = targetIds.contains(targetType) ? targetIds.at(targetType) : std::set<int>{};
                            auto groupIds =
                                targetGroupIds.contains(targetType) ? targetGroupIds.at(targetType) : std::set<int>{};
                            for (int i = (int)container->children.size() - 1; i >= 0; --i)
                            {
                              auto& item = container->children[i];
                              if (item.type == ElementType::GROUP && groupIds.contains(item.id))
                              {
                                container->children.erase(container->children.begin() + i);
                                continue;
                              }
                              if (item.type == targetTrackType &&
                                  (ids.contains(track_id_get(item, targetType)) || groupIds.contains(item.groupId)))
                                container->children.erase(container->children.begin() + i);
                            }
                          }
                          reference_clear_for(document);
                          group_selection_reset_for(document);
                        });
    };

    auto item_references_groupable_get = [&]()
    {
      if (!document.groupReferences.empty()) return std::vector<Reference>{};
      auto selectedItems = item_references_for_current_get();
      std::erase_if(selectedItems, [](const Reference& itemReference)
                    { return itemReference.itemType != LAYER && itemReference.itemType != NULL_; });
      if (selectedItems.empty()) return std::vector<Reference>{};
      auto itemType = selectedItems.begin()->itemType;
      for (const auto& itemReference : selectedItems)
        if (itemReference.itemType != itemType) return std::vector<Reference>{};

      std::vector<Reference> result{};
      for (const auto& row : timeline_item_rows_get())
      {
        if (row.isGroup || row.type != itemType) continue;
        auto itemReference = item_reference_get(row.type, row.id);
        if (!selectedItems.contains(itemReference)) continue;
        if (row.groupId != -1) return std::vector<Reference>{};
        result.push_back(itemReference);
      }
      return result;
    };

    auto item_group = [&]()
    {
      auto targetReferences = item_references_groupable_get();
      if (targetReferences.empty()) return;
      auto targetType = targetReferences.front().itemType;
      auto animationIndex = reference.animationIndex;
      auto groupName = std::string(localize.get(TEXT_NEW_GROUP));

      edit_command_push(EDIT_GROUP_ITEMS, Document::ITEMS,
                        [=](Manager&, Document& document) mutable
                        {
                          auto animation = command_animation_get(document, animationIndex);
                          if (!animation) return;

                          auto containerType = container_type_get(targetType);
                          auto targetTrackType = track_type_get(targetType);
                          auto container = element_child_first_get(*animation, containerType);
                          if (!container) return;

                          std::set<int> targetIds{};
                          for (auto itemReference : targetReferences)
                            targetIds.insert(itemReference.itemID);

                          auto group = element_make(ElementType::GROUP);
                          group.id = element_child_next_id_get(*container, ElementType::GROUP);
                          group.name = groupName;
                          group.isExpanded = true;
                          int insertIndex = (int)container->children.size();

                          for (int i = 0; i < (int)container->children.size(); ++i)
                          {
                            auto& item = container->children[i];
                            if (item.type == targetTrackType && targetIds.contains(track_id_get(item, targetType)))
                            {
                              insertIndex = std::min(insertIndex, i);
                              item.groupId = group.id;
                            }
                          }

                          if (insertIndex == (int)container->children.size()) return;
                          insertIndex = std::clamp(insertIndex, 0, (int)container->children.size());
                          container->children.insert(container->children.begin() + insertIndex, group);

                          document.items.references.clear();
                          for (auto itemReference : targetReferences)
                            document.items.references.insert(itemReference);
                          document.reference = targetReferences.front();
                          frames_selection_reset_for(document);
                        });
    };

    auto rows_move_to_row = [&](std::vector<TimelineRowReference> draggedRows, TimelineItemRow targetRow,
                                bool isDropAfter, bool isDropIntoGroup = false)
    {
      if (draggedRows.empty()) return;
      auto targetType = draggedRows.front().type;
      if (targetType != LAYER && targetType != NULL_) return;
      for (const auto& draggedRow : draggedRows)
        if (draggedRow.type != targetType) return;

      if (targetRow.isGroup && targetRow.type != targetType) return;
      if (!targetRow.isGroup && targetRow.type != targetType && targetRow.type != NONE && targetRow.type != ROOT &&
          targetRow.type != TRIGGER)
        return;

      auto animationIndex = reference.animationIndex;
      edit_command_push(EDIT_MOVE_ITEMS, Document::ITEMS,
                        [=, this](Manager&, Document& document) mutable
                        {
                          auto animation = command_animation_get(document, animationIndex);
                          if (!animation) return;
                          auto container = element_child_first_get(*animation, container_type_get(targetType));
                          if (!container) return;
                          auto targetTrackType = track_type_get(targetType);

                          std::set<int> draggedIds{};
                          std::set<int> draggedGroupIds{};
                          for (const auto& draggedRow : draggedRows)
                          {
                            if (draggedRow.isGroup)
                              draggedGroupIds.insert(draggedRow.id);
                            else
                              draggedIds.insert(draggedRow.id);
                          }

                          if (targetRow.groupId != -1 && draggedGroupIds.contains(targetRow.groupId)) return;

                          std::vector<Element> movedItems{};
                          for (const auto& item : container->children)
                          {
                            auto isDraggedGroup = item.type == ElementType::GROUP && draggedGroupIds.contains(item.id);
                            auto isDraggedTrack = item.type == targetTrackType &&
                                                  (draggedIds.contains(track_id_get(item, targetType)) ||
                                                   draggedGroupIds.contains(item.groupId));
                            if (isDraggedGroup || isDraggedTrack) movedItems.push_back(item);
                          }
                          if (movedItems.empty()) return;

                          if (targetRow.isGroup && draggedGroupIds.contains(targetRow.id)) return;

                          auto row_index_get = [&](const TimelineItemRow& row)
                          {
                            for (int i = 0; i < (int)container->children.size(); ++i)
                            {
                              auto& item = container->children[i];
                              if (row.isGroup && item.type == ElementType::GROUP && item.id == row.id) return i;
                              if (!row.isGroup && row.type == targetType && item.type == targetTrackType &&
                                  track_id_get(item, targetType) == row.id)
                                return i;
                            }
                            return -1;
                          };

                          int targetIndex = (int)container->children.size();
                          if (targetRow.isGroup || targetRow.type == targetType)
                          {
                            auto rowIndex = row_index_get(targetRow);
                            if (rowIndex == -1) return;
                            targetIndex = rowIndex + (targetType == LAYER ? !isDropAfter : isDropAfter);
                          }

                          int removedBeforeTarget = 0;
                          for (int i = (int)container->children.size() - 1; i >= 0; --i)
                          {
                            auto& item = container->children[i];
                            auto isDraggedGroup = item.type == ElementType::GROUP && draggedGroupIds.contains(item.id);
                            auto isDraggedTrack = item.type == targetTrackType &&
                                                  (draggedIds.contains(track_id_get(item, targetType)) ||
                                                   draggedGroupIds.contains(item.groupId));
                            if (isDraggedGroup || isDraggedTrack)
                            {
                              if (i < targetIndex) ++removedBeforeTarget;
                              container->children.erase(container->children.begin() + i);
                            }
                          }

                          auto targetGroupId = -1;
                          if (targetRow.isGroup && isDropIntoGroup)
                            targetGroupId = targetRow.id;
                          else if (targetRow.type == targetType)
                            targetGroupId = targetRow.groupId;

                          for (auto& item : movedItems)
                            if (item.type == targetTrackType && !draggedGroupIds.contains(item.groupId))
                              item.groupId = targetGroupId;

                          targetIndex -= removedBeforeTarget;
                          targetIndex = std::clamp(targetIndex, 0, (int)container->children.size());
                          container->children.insert(container->children.begin() + targetIndex, movedItems.begin(),
                                                     movedItems.end());

                          document.items.references.clear();
                          document.groupReferences.clear();
                          for (const auto& draggedRow : draggedRows)
                          {
                            if (draggedRow.isGroup)
                              document.groupReferences.insert(
                                  {draggedRow.animationIndex, draggedRow.type, draggedRow.id});
                            else
                              document.items.references.insert(row_item_reference_get(draggedRow));
                          }
                          if (!document.items.references.empty())
                            document.reference = *document.items.references.begin();
                          else
                            document.reference = {animationIndex};
                          frames_selection_reset_for(document);
                        });
    };

    auto fit_animation_length = [&]()
    {
      if (!animation) return;
      auto animationIndex = reference.animationIndex;
      edit_command_push(EDIT_FIT_ANIMATION_LENGTH, Document::ANIMATIONS,
                        [=](Manager&, Document& document)
                        {
                          auto animation = command_animation_get(document, animationIndex);
                          if (!animation) return;
                          animation->frameNum = animation_length_get(*animation);
                        });
    };

    auto context_menu = [&]()
    {
      ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
      ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);

      auto copy = [&]()
      {
        if (!animation) return;
        auto selectedFrames = frame_references_for_current_get();
        if (selectedFrames.empty()) return;

        std::string clipboardString{};
        for (auto frameReference : selectedFrames)
        {
          auto item = item_get(frameReference.itemType, frameReference.itemID);
          auto frame = item ? track_frame_get(*item, frameReference.frameIndex) : nullptr;
          if (!frame) continue;
          auto parentType = item_type_to_track_type_get(item_type_get(frameReference.itemType));
          clipboardString += element_to_string(*frame, parentType);
        }
        if (!clipboardString.empty()) clipboard.set(clipboardString);
      };

      auto cut = [&]()
      {
        copy();
        auto selectedFrames = frame_references_for_current_get();
        edit_command_push(EDIT_CUT_FRAMES, Document::FRAMES,
                          [=](Manager&, Document& document) mutable
                          { frames_delete_for(document, selectedFrames); });
      };

      auto paste = [&]()
      {
        if (clipboard.is_empty()) return;
        auto targetReference = reference;
        auto selectedFrames = frame_references_for_current_get();
        auto clipboardString = clipboard.get();
        auto targetHoveredTime = hoveredTime;
        auto message = std::string(localize.get(EDIT_PASTE_FRAMES));
        command_push([=](Manager&, Document& document) mutable
                     {
                       document.snapshot(message);
                       auto animation = command_animation_get(document, targetReference.animationIndex);
                       if (!animation) return;
                       if (auto item = command_item_get(document, targetReference.animationIndex,
                                                        targetReference.itemType, targetReference.itemID))
                       {
                         std::set<int> indices{};
                         std::string errorString{};
                         int insertIndex = (int)item->children.size();
                         std::set<int> selectedIndices{};
                         for (auto frameReference : selectedFrames)
                           if (is_same_item(frameReference, targetReference))
                             selectedIndices.insert(frameReference.frameIndex);

                         if (!selectedIndices.empty())
                           insertIndex = std::min((int)item->children.size(), *selectedIndices.rbegin() + 1);
                         else if (targetReference.frameIndex >= 0 &&
                                  targetReference.frameIndex < (int)item->children.size())
                           insertIndex = targetReference.frameIndex + 1;

                         auto start = targetReference.itemType == TRIGGER ? targetHoveredTime : insertIndex;
                         if (frames_deserialize(*item, clipboardString, start, indices, &errorString))
                         {
                           if (targetReference.itemType != LAYER)
                           {
                             for (auto i : indices)
                               if (auto frame = track_frame_get(*item, i); frame)
                               {
                                 frame->regionId = -1;
                                 frame->crop = {};
                                 frame->size = {};
                                 frame->pivot = {};
                               }
                           }
                           else if (targetReference.itemID != -1)
                           {
                             auto layer = command_layer_get(document, targetReference.itemID);
                             auto spritesheet =
                                 layer ? command_spritesheet_get(document, layer->spritesheetId) : nullptr;

                             for (auto i : indices)
                             {
                               auto frame = track_frame_get(*item, i);
                               if (!frame || frame->regionId == -1) continue;
                               auto region = spritesheet ? element_child_id_get(*spritesheet, ElementType::REGION,
                                                                                 frame->regionId)
                                                         : nullptr;
                               if (!region) frame->regionId = -1;
                             }
                           }

                           document.reference = targetReference;
                           document.frames.selection.clear();
                           document.frames.references.clear();
                           for (auto i : indices)
                           {
                             document.frames.selection.insert(i);
                             document.frames.references.insert(
                                 {targetReference.animationIndex, targetReference.itemType, targetReference.itemID, i});
                           }
                           document.reference.frameIndex = *indices.begin();
                           document.anm2_change(Document::FRAMES);
                         }
                         else
                         {
                           toasts.push(std::format("{} {}", localize.get(TOAST_DESERIALIZE_FRAMES_FAILED),
                                                   errorString));
                           logger.error(std::format("{} {}", localize.get(TOAST_DESERIALIZE_FRAMES_FAILED,
                                                                          anm2ed::ENGLISH),
                                                    errorString));
                         }
                       }
                       else
                       {
                         toasts.push(localize.get(TOAST_DESERIALIZE_FRAMES_NO_SELECTION));
                         logger.warning(localize.get(TOAST_DESERIALIZE_FRAMES_NO_SELECTION, anm2ed::ENGLISH));
                       }
                     });
      };

      if (shortcut(manager.chords[SHORTCUT_CUT], shortcut::FOCUSED)) cut();
      if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
      if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
      if (shortcut(manager.chords[SHORTCUT_SPLIT], shortcut::FOCUSED)) frame_split();
      if (shortcut(manager.chords[SHORTCUT_BAKE], shortcut::FOCUSED)) frames_bake();
      if (shortcut(manager.chords[SHORTCUT_FIT], shortcut::FOCUSED)) fit_animation_length();

      auto make_region = [&]()
      {
        auto targetReference = reference;
        auto frame = frame_get();
        if (!frame || targetReference.itemType != LAYER || targetReference.itemID == -1) return;
        if (frame->regionId != -1) return;
        auto layer = layer_get(targetReference.itemID);
        if (!layer) return;

        auto spritesheetID = layer->spritesheetId;
        if (!spritesheet_get(spritesheetID)) return;

        auto settingsPtr = &settings;
        command_push([=](Manager& manager, Document& document)
                     {
                       auto frame = command_frame_get(document, targetReference);
                       if (!frame || frame->regionId != -1) return;
                       auto layer = command_layer_get(document, targetReference.itemID);
                       if (!layer) return;

                       auto spritesheetID = layer->spritesheetId;
                       if (!command_spritesheet_get(document, spritesheetID)) return;

                       auto region = element_make(ElementType::REGION);
                       region.crop = frame->crop;
                       region.size = frame->size;
                       region.pivot = frame->pivot;
                       region.origin = Origin::CUSTOM;

                       document.spritesheet.reference = spritesheetID;
                       settingsPtr->windowIsRegions = true;
                       manager.makeRegionSpritesheetId = spritesheetID;
                       manager.makeRegion = region;
                       manager.isMakeRegionRequested = true;
                     });
      };

      auto item = item_get(reference.itemType, reference.itemID);
      auto frame = frame_get();
      auto selectedFrames = frame_references_for_current_get();
      auto selectedBakeFrames = selectedFrames;
      std::erase_if(selectedBakeFrames,
                    [](const Reference& frameReference) { return frameReference.itemType == TRIGGER; });
      bool isMakeRegion = frame && reference.itemType == LAYER && reference.itemID != -1 && frame->regionId == -1 &&
                          layer_get(reference.itemID) && spritesheet_get(layer_get(reference.itemID)->spritesheetId);
      Actions actions{};
      actions_undo_redo_add(actions, manager, document);
      actions.separator();
      actions.add({.label = playback.isPlaying ? LABEL_PAUSE : LABEL_PLAY,
                   .shortcut = SHORTCUT_PLAY_PAUSE,
                   .isEnabled = []() { return true; },
                   .run = [&]() { playback.toggle(); }});
      actions.add({.label = LABEL_INSERT,
                   .shortcut = SHORTCUT_INSERT_FRAME,
                   .isEnabled = [&]() { return item; },
                   .run = [&]() { frame_insert(); }});
      actions.add({.label = LABEL_DELETE,
                   .shortcut = SHORTCUT_REMOVE,
                   .isEnabled = [=]() { return !selectedFrames.empty(); },
                   .run = [&]() { frames_delete_action(); }});
      actions.add({.label = LABEL_BAKE,
                   .shortcut = SHORTCUT_BAKE,
                   .isEnabled = [=]() { return !selectedBakeFrames.empty(); },
                   .run = [&]() { frames_bake(); }});
      actions.add({.label = LABEL_FIT_ANIMATION_LENGTH,
                   .shortcut = SHORTCUT_FIT,
                   .isEnabled = [&]() { return animation && animation->frameNum != animation_length_get(*animation); },
                   .run = [&]() { fit_animation_length(); }});
      actions.add({.label = LABEL_SPLIT,
                   .shortcut = SHORTCUT_SPLIT,
                   .isEnabled = [=]() { return selectedBakeFrames.size() == 1; },
                   .run = [&]() { frame_split(); }});
      actions.add({.label = LABEL_MAKE_REGION,
                   .shortcut = -1,
                   .isEnabled = [&]() { return isMakeRegion; },
                   .run = [&]() { make_region(); }});
      actions.separator();
      actions.add(ACTION_CUT, [=]() { return !selectedFrames.empty(); }, cut);
      actions.add(ACTION_COPY, [=]() { return !selectedFrames.empty(); }, copy);
      actions.add(ACTION_PASTE, [&]() { return !clipboard.is_empty(); }, paste);
      actions_context_window_draw("##Context Menu", actions, settings);

      ImGui::PopStyleVar(2);
    };

    auto item_base_properties_open = [&](int type, int id)
    {
      switch (type)
      {
        case LAYER:
          manager.layer_properties_open(id);
          break;
        case NULL_:
          manager.null_properties_open(id);
        default:
          break;
      };
    };

    auto group_properties_close = [&]()
    {
      groupName.clear();
      groupAnimationIndex = -1;
      groupType = NONE;
      groupId = -1;
      groupPropertiesPopup.close();
    };

    auto group_properties_open = [&](const TimelineItemRow& row, const Element& group)
    {
      groupName = group.name.empty() ? std::string(localize.get(TEXT_NEW_GROUP)) : group.name;
      groupAnimationIndex = reference.animationIndex;
      groupType = row.type;
      groupId = row.id;
      groupPropertiesPopup.open();
    };

    auto group_properties_update = [&]()
    {
      groupPropertiesPopup.trigger();

      if (ImGui::BeginPopupModal(groupPropertiesPopup.label(), &groupPropertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
      {
        auto childSize = child_size_get(1);
        if (ImGui::BeginChild("##Group Properties Child", childSize, ImGuiChildFlags_Borders))
        {
          if (groupPropertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
          input_text_string(localize.get(BASIC_NAME), &groupName);
          ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ITEM_NAME));
        }
        ImGui::EndChild();

        auto widgetSize = widget_size_with_row_get(2);

        shortcut(manager.chords[SHORTCUT_CONFIRM]);
        if (ImGui::Button(localize.get(BASIC_CONFIRM), widgetSize))
        {
          auto targetName = groupName;
          auto targetAnimationIndex = groupAnimationIndex;
          auto targetType = groupType;
          auto targetId = groupId;
          edit_command_push(EDIT_RENAME_GROUP, Document::ITEMS,
                            [=](Manager&, Document& document)
                            {
                              auto animation = command_animation_get(document, targetAnimationIndex);
                              auto container =
                                  animation ? element_child_first_get(*animation, container_type_get(targetType))
                                            : nullptr;
                              auto group = container ? element_child_id_get(*container, ElementType::GROUP, targetId)
                                                     : nullptr;
                              if (!group) return;
                              group->name = targetName;
                            });
          group_properties_close();
        }

        ImGui::SameLine();

        shortcut(manager.chords[SHORTCUT_CANCEL]);
        if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) group_properties_close();

        ImGui::EndPopup();
      }

      groupPropertiesPopup.end();
    };

    auto item_context_menu = [&]()
    {
      ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
      ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);

      auto& type = reference.itemType;
      auto& id = reference.itemID;
      auto item = selected_item_get();
      auto selectedItems = item_references_for_current_get();
      auto selectedRows = selected_row_references_get();
      auto selectedGroupableItems = item_references_groupable_get();
      TimelineItemRow selectedGroupRow{};
      Element* selectedGroup{};
      if (selectedRows.size() == 1 && selectedRows.front().isGroup)
      {
        auto row = selectedRows.front();
        selectedGroupRow = {.type = row.type, .id = row.id, .index = row.index, .isGroup = true};
        selectedGroup = row_group_get(selectedGroupRow);
      }
      auto isRemoveAvailable = std::ranges::any_of(selectedRows, [](const TimelineRowReference& row)
      { return row.type == LAYER || row.type == NULL_; });

      if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow) &&
          ImGui::IsMouseClicked(ImGuiMouseButton_Right))
        ImGui::OpenPopup("##Items Context Menu");

      Actions actions{};
      actions_undo_redo_add(actions, manager, document);
      actions.separator();
      actions.add(ACTION_PROPERTIES, [&]() { return selectedGroup || (item && (type == LAYER || type == NULL_)); },
                  [&]()
                  {
                    if (selectedGroup)
                      group_properties_open(selectedGroupRow, *selectedGroup);
                    else
                      item_base_properties_open(type, id);
                  });
      actions.add(ACTION_ADD, [&]() { return animation; }, [&]() { itemProperties.open(); });
      actions.add(ACTION_REMOVE, [=]() { return isRemoveAvailable; }, [&]() { item_remove(); });
      actions.add(ACTION_GROUP, [=]() { return !selectedGroupableItems.empty(); }, [&]() { item_group(); });
      actions_popup_draw("##Items Context Menu", actions, settings);

      ImGui::PopStyleVar(2);
    };

    auto item_child = [&](const TimelineItemRow& row, int index)
    {
      ImGui::PushID(index);

      auto type = row.type;
      auto id = row.id;
      if (row.isGroup)
      {
        auto group = row_group_get(row);
        if (!group)
        {
          ImGui::PopID();
          return;
        }

        auto label = group->name.empty() ? std::string(localize.get(TEXT_NEW_GROUP)) : group->name;
        auto itemSize = ImVec2(ImGui::GetContentRegionAvail().x, rowFrameChildHeight);
        auto isGroupVisible = group->isVisible;
        auto colorVec = item_color_vec(type);
        if (is_group_selected(row))
        {
          if (isLightTheme)
            colorVec = ITEM_COLOR_LIGHT_SELECTED[type_index(type)];
          else
            colorVec = item_color_active_vec(type);
        }
        auto color = to_imvec4(isGroupVisible ? colorVec : colorVec * COLOR_HIDDEN_MULTIPLIER);
        ImGui::PushStyleColor(ImGuiCol_ChildBg, color);
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);

        if (ImGui::BeginChild("##Group Child", itemSize, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollWithMouse))
        {
          auto cursorPos = ImGui::GetCursorPos();
          auto groupChildMin = ImGui::GetWindowPos();
          auto groupChildMax = ImVec2(groupChildMin.x + ImGui::GetWindowSize().x,
                                      groupChildMin.y + ImGui::GetWindowSize().y);

          auto toggle_group = [&]()
          {
            auto targetRow = row;
            auto targetAnimationIndex = reference.animationIndex;
            edit_command_push(EDIT_TOGGLE_GROUP_EXPANDED, Document::ITEMS,
                              [=](Manager&, Document& document) mutable
                              {
                                auto animation = command_animation_get(document, targetAnimationIndex);
                                auto container =
                                    animation ? element_child_first_get(*animation, container_type_get(targetRow.type))
                                              : nullptr;
                                auto group = container ? element_child_id_get(*container, ElementType::GROUP,
                                                                              targetRow.id)
                                                       : nullptr;
                                if (!group) return;
                                group->isExpanded = !group->isExpanded;
                              });
          };

          ImGui::SetCursorPos(to_imvec2(to_vec2(cursorPos) - to_vec2(style.ItemSpacing)));
          ImGui::SetNextItemAllowOverlap();
          ImGui::InvisibleButton("##Group Button", itemSize);
          auto groupButtonMin = ImGui::GetItemRectMin();
          auto groupButtonMax = ImGui::GetItemRectMax();
          auto mousePos = ImGui::GetIO().MousePos;
          auto is_mouse_in_rect = [](ImVec2 mouse, ImVec2 min, ImVec2 max)
          { return mouse.x >= min.x && mouse.x < max.x && mouse.y >= min.y && mouse.y < max.y; };
          auto isGroupHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) &&
                                is_mouse_in_rect(mousePos, groupButtonMin, groupButtonMax);
          bool isGroupTooltipDelayed{};
          if (isGroupHovered)
          {
            auto& imguiStyle = ImGui::GetStyle();
            auto previousTooltipDelay = imguiStyle.HoverDelayNormal;
            imguiStyle.HoverDelayNormal = FRAME_TOOLTIP_HOVER_DELAY;
            isGroupTooltipDelayed =
                ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayNormal |
                                     ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_NoSharedDelay);
            imguiStyle.HoverDelayNormal = previousTooltipDelay;
          }

          if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
          {
            rowDragReferences = row_drag_references_get(row);
            ImGui::SetDragDropPayload("Timeline Row Drag Drop", rowDragReferences.data(),
                                      (int)rowDragReferences.size() * (int)sizeof(TimelineRowReference));
            row_drag_tooltip_draw(rowDragReferences);
            ImGui::EndDragDropSource();
          }

          if (ImGui::BeginDragDropTarget())
          {
            if (auto payload = ImGui::AcceptDragDropPayload(
                    "Timeline Row Drag Drop",
                    ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect))
            {
              auto groupDropThird = (groupButtonMax.y - groupButtonMin.y) / 3.0f;
              auto dropZone = DropZone::BEFORE;
              if (mousePos.y >= groupButtonMax.y - groupDropThird)
                dropZone = DropZone::AFTER;
              else if (mousePos.y >= groupButtonMin.y + groupDropThird)
                dropZone = DropZone::INSIDE;
              auto isDropAfter = dropZone != DropZone::BEFORE;
              auto payloadRows = (TimelineRowReference*)payload->Data;
              auto payloadCount = payload->DataSize / sizeof(TimelineRowReference);
              std::vector<TimelineRowReference> draggedRows(payloadRows, payloadRows + payloadCount);
              auto isDropIntoGroup = dropZone == DropZone::INSIDE && (row.type == LAYER || row.type == NULL_);
              for (auto draggedRow : draggedRows)
                if (draggedRow.isGroup || draggedRow.type != row.type) isDropIntoGroup = false;

              if (isDropIntoGroup)
                drop_box_draw(ImGui::GetWindowDrawList(), groupChildMin, groupChildMax);
              else
                drop_line_draw(ImGui::GetWindowDrawList(), groupChildMin, groupChildMax, isDropAfter);

              if (payload->IsDelivery()) rows_move_to_row(draggedRows, row, isDropAfter, isDropIntoGroup);
            }
            ImGui::EndDragDropTarget();
          }

          ImGui::SetCursorPos(cursorPos);
          auto folderIcon = group->isExpanded ? icon::FOLDER_OPEN : icon::FOLDER;
          ImGui::Image(resources.icons[folderIcon].id, icon_size_get());
          auto iconMin = ImGui::GetItemRectMin();
          auto iconMax = ImGui::GetItemRectMax();
          overlay_icon(resources.icons[folderIcon].id, itemIconTint);
          ImGui::SameLine();

          ImGui::PushStyleColor(ImGuiCol_Text, itemTextColor);
          ImGui::TextUnformatted(label.c_str());
          ImGui::PopStyleColor();

          ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
          ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4());
          ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4());
          ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2());
          ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);

          ImGui::SetCursorPos(
              ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x,
                     (itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
          int visibleIcon = isGroupVisible ? icon::VISIBLE : icon::INVISIBLE;
          if (ImGui::ImageButton("##Group Visible Toggle", resources.icons[visibleIcon].id, icon_size_get()))
          {
            auto targetAnimationIndex = reference.animationIndex;
            auto targetType = type;
            auto targetGroupId = group->id;
            auto targetVisible = !isGroupVisible;
            edit_command_push(EDIT_TOGGLE_ITEM_VISIBILITY, Document::FRAMES,
                              [=](Manager&, Document& document)
                              {
                                auto animation = command_animation_get(document, targetAnimationIndex);
                                auto container =
                                    animation ? element_child_first_get(*animation, container_type_get(targetType))
                                              : nullptr;
                                if (!container) return;
                                auto group = element_child_id_get(*container, ElementType::GROUP, targetGroupId);
                                if (!group) return;
                                group->isVisible = targetVisible;
                              });
          }
          auto visibleButtonMin = ImGui::GetItemRectMin();
          auto visibleButtonMax = ImGui::GetItemRectMax();
          overlay_icon(resources.icons[visibleIcon].id, itemIconTint);
          ImGui::SetItemTooltip("%s", isGroupVisible ? localize.get(TOOLTIP_ITEM_VISIBILITY_SHOWN)
                                                     : localize.get(TOOLTIP_ITEM_VISIBILITY_HIDDEN));
          ImGui::PopStyleVar(2);
          ImGui::PopStyleColor(3);

          auto isIconHovered = isGroupHovered && is_mouse_in_rect(mousePos, iconMin, iconMax);
          auto isVisibleButtonHovered = isGroupHovered && is_mouse_in_rect(mousePos, visibleButtonMin, visibleButtonMax);
          if (isGroupTooltipDelayed && !isVisibleButtonHovered)
          {
            ImGui::BeginTooltip();
            ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
            ImGui::TextUnformatted(label.c_str());
            ImGui::PopFont();
            ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(group->id)).c_str());
            auto groupItemsCount = group_items_count_get(type, group->id);
            ImGui::TextUnformatted(
                std::vformat(localize.get(FORMAT_ITEMS_COUNT), std::make_format_args(groupItemsCount)).c_str());
            ImGui::EndTooltip();
          }
          if (isIconHovered && ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
              !ImGui::IsMouseDragPastThreshold(ImGuiMouseButton_Left))
            toggle_group();
          else if (isGroupHovered && !isIconHovered && !isVisibleButtonHovered &&
                   ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
            group_properties_open(row, *group);
          else if (isGroupHovered && !isIconHovered && !isVisibleButtonHovered &&
                   ImGui::IsMouseReleased(ImGuiMouseButton_Left) && !ImGui::IsMouseDragPastThreshold(ImGuiMouseButton_Left))
            row_selection_set(row);
        }
        ImGui::EndChild();
        ImGui::PopStyleVar(2);
        ImGui::PopStyleColor();
        ImGui::PopID();
        return;
      }

      auto item = item_get(type, id);
      if (type != NONE && !item)
      {
        ImGui::PopID();
        return;
      }
      auto isItemVisible = item ? item->isVisible : false;
      auto isVisible = item ? item->isVisible && is_track_group_visible(type, row.groupId) : false;
      auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
      if (isOnlyShowLayers && type != LAYER) isVisible = false;
      auto isReferenced = reference.itemType == type && reference.itemID == id;
      auto isItemSelected = is_row_selected(row);

      auto label = [&]() -> std::string
      {
        if (type == LAYER)
        {
          auto layer = layer_get(id);
          if (!layer) return localize.get(TYPE_STRINGS[type]);
          return std::vformat(localize.get(FORMAT_LAYER), std::make_format_args(id, layer->name, layer->spritesheetId));
        }
        if (type == NULL_)
        {
          auto null = null_get(id);
          if (!null) return localize.get(TYPE_STRINGS[type]);
          return std::vformat(localize.get(FORMAT_NULL), std::make_format_args(id, null->name));
        }
        return localize.get(TYPE_STRINGS[type]);
      }();
      auto icon = TYPE_ICONS[type];
      auto iconTintCurrent = isLightTheme && type == NONE ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : itemIconTint;
      auto baseColorVec = item_color_vec(type);
      auto activeColorVec = item_color_active_vec(type);
      auto colorVec = baseColorVec;
      if (isItemSelected && type != NONE)
      {
        if (isLightTheme)
          colorVec = ITEM_COLOR_LIGHT_SELECTED[type_index(type)];
        else
          colorVec = activeColorVec;
      }
      auto color = to_imvec4(colorVec);
      color = !isVisible ? to_imvec4(colorVec * COLOR_HIDDEN_MULTIPLIER) : color;
      ImGui::PushStyleColor(ImGuiCol_ChildBg, color);

      ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
      ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);

      auto itemSize = ImVec2(ImGui::GetContentRegionAvail().x, rowFrameChildHeight);

      if (ImGui::BeginChild(label.c_str(), itemSize, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollWithMouse))
      {
        auto cursorPos = ImGui::GetCursorPos();
        auto itemChildMin = ImGui::GetWindowPos();
        auto itemChildMax = ImVec2(itemChildMin.x + ImGui::GetWindowSize().x, itemChildMin.y + ImGui::GetWindowSize().y);

        if (type != NONE)
        {
          ImGui::SetCursorPos(to_imvec2(to_vec2(cursorPos) - to_vec2(style.ItemSpacing)));
          ImGui::SetNextItemAllowOverlap();
          ImGui::SetNextItemStorageID(id);
          ImGui::InvisibleButton("##Item Button", itemSize);
          auto itemButtonMin = ImGui::GetItemRectMin();
          auto itemButtonMax = ImGui::GetItemRectMax();
          auto isItemButtonHovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);

          if (type == LAYER || type == NULL_)
          {
            if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
            {
              rowDragReferences = row_drag_references_get(row);
              ImGui::SetDragDropPayload("Timeline Row Drag Drop", rowDragReferences.data(),
                                        (int)rowDragReferences.size() * (int)sizeof(TimelineRowReference));
              row_drag_tooltip_draw(rowDragReferences);
              ImGui::EndDragDropSource();
            }

            if (ImGui::BeginDragDropTarget())
            {
              if (auto payload = ImGui::AcceptDragDropPayload(
                      "Timeline Row Drag Drop",
                      ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect))
              {
                auto isDropAfter = is_drop_after(itemButtonMin, itemButtonMax);
                drop_line_draw(ImGui::GetWindowDrawList(), itemChildMin, itemChildMax, isDropAfter);

                auto payloadRows = (TimelineRowReference*)payload->Data;
                auto payloadCount = payload->DataSize / sizeof(TimelineRowReference);
                std::vector<TimelineRowReference> draggedRows(payloadRows, payloadRows + payloadCount);
                if (payload->IsDelivery()) rows_move_to_row(draggedRows, row, isDropAfter);
              }
              ImGui::EndDragDropTarget();
            }
          }

          if (isItemButtonHovered)
          {
            if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) item_base_properties_open(type, id);
            else if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
                     !ImGui::IsMouseDragPastThreshold(ImGuiMouseButton_Left))
              row_selection_set(row);

            auto& imguiStyle = ImGui::GetStyle();
            auto previousTooltipFlags = imguiStyle.HoverFlagsForTooltipMouse;
            auto previousTooltipDelay = imguiStyle.HoverDelayNormal;
            imguiStyle.HoverFlagsForTooltipMouse = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayNormal |
                                                   ImGuiHoveredFlags_AllowWhenDisabled |
                                                   ImGuiHoveredFlags_NoSharedDelay;
            imguiStyle.HoverDelayNormal = FRAME_TOOLTIP_HOVER_DELAY;
            bool showItemTooltip = ImGui::BeginItemTooltip();
            imguiStyle.HoverFlagsForTooltipMouse = previousTooltipFlags;
            imguiStyle.HoverDelayNormal = previousTooltipDelay;

            if (showItemTooltip)
            {
              auto yesNoLabel = [&](bool value) { return value ? localize.get(BASIC_YES) : localize.get(BASIC_NO); };
              auto visibleLabel = yesNoLabel(isVisible);
              auto framesCount = item_frames_count(item);

              switch (type)
              {
                case ROOT:
                {
                  ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
                  ImGui::TextUnformatted(localize.get(BASIC_ROOT));
                  ImGui::PopFont();

                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_VISIBLE), std::make_format_args(visibleLabel)).c_str());
                  auto transformLabel = yesNoLabel(settings.previewIsRootTransform);
                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_TRANSFORM), std::make_format_args(transformLabel)).c_str());
                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_FRAMES_COUNT), std::make_format_args(framesCount)).c_str());
                  break;
                }
                case LAYER:
                {
                  auto layer = layer_get(id);
                  if (!layer) break;
                  ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
                  ImGui::TextUnformatted(layer->name.c_str());
                  ImGui::PopFont();

                  ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_SPRITESHEET_ID), std::make_format_args(layer->spritesheetId))
                          .c_str());
                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_VISIBLE), std::make_format_args(visibleLabel)).c_str());
                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_FRAMES_COUNT), std::make_format_args(framesCount)).c_str());
                  break;
                }
                case NULL_:
                {
                  auto nullInfo = null_get(id);
                  if (!nullInfo) break;
                  ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
                  ImGui::TextUnformatted(nullInfo->name.c_str());
                  ImGui::PopFont();

                  auto rectLabel = yesNoLabel(nullInfo->isShowRect);
                  ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_RECT), std::make_format_args(rectLabel)).c_str());
                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_VISIBLE), std::make_format_args(visibleLabel)).c_str());
                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_FRAMES_COUNT), std::make_format_args(framesCount)).c_str());
                  break;
                }
                case TRIGGER:
                {
                  ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
                  ImGui::TextUnformatted(localize.get(BASIC_TRIGGERS));
                  ImGui::PopFont();

                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_VISIBLE), std::make_format_args(visibleLabel)).c_str());
                  ImGui::TextUnformatted(
                      std::vformat(localize.get(FORMAT_TRIGGERS_COUNT), std::make_format_args(framesCount)).c_str());
                  break;
                }
                default:
                  break;
              }

              ImGui::EndTooltip();
            }
          }

          auto contentCursorPos = cursorPos;
          contentCursorPos.x += (float)row.depth * ImGui::GetTextLineHeightWithSpacing();
          ImGui::SetCursorPos(contentCursorPos);

          ImGui::Image(resources.icons[icon].id, icon_size_get());
          overlay_icon(resources.icons[icon].id, iconTintCurrent);
          ImGui::SameLine();
          if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
          ImGui::PushStyleColor(ImGuiCol_Text, itemTextColor);
          ImGui::TextUnformatted(label.c_str());
          ImGui::PopStyleColor();
          if (isReferenced) ImGui::PopFont();

          ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
          ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4());
          ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4());
          ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2());
          ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);

          ImGui::SetCursorPos(
              ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x,
                     (itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
          int visibleIcon = isItemVisible ? icon::VISIBLE : icon::INVISIBLE;
          if (ImGui::ImageButton("##Visible Toggle", resources.icons[visibleIcon].id, icon_size_get()))
          {
            auto animationIndex = reference.animationIndex;
            auto targetType = type;
            auto targetID = id;
            edit_command_push(EDIT_TOGGLE_ITEM_VISIBILITY, Document::FRAMES,
                              [=](Manager&, Document& document)
                              {
                                auto item = command_item_get(document, animationIndex, targetType, targetID);
                                if (!item) return;
                                item->isVisible = !item->isVisible;
                              });
          }
          overlay_icon(resources.icons[visibleIcon].id, iconTintCurrent);
          ImGui::SetItemTooltip("%s", isItemVisible ? localize.get(TOOLTIP_ITEM_VISIBILITY_SHOWN)
                                                    : localize.get(TOOLTIP_ITEM_VISIBILITY_HIDDEN));

          if (type == NULL_)
          {
            if (auto null = null_get(id))
            {
              auto& isShowRect = null->isShowRect;
              auto rectIcon = isShowRect ? icon::SHOW_RECT : icon::HIDE_RECT;
              ImGui::SetCursorPos(
                  ImVec2(itemSize.x - (ImGui::GetTextLineHeightWithSpacing() * 2) - ImGui::GetStyle().ItemSpacing.x,
                         (itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
              if (ImGui::ImageButton("##Rect Toggle", resources.icons[rectIcon].id, icon_size_get()))
              {
                auto nullID = id;
                edit_command_push(EDIT_TOGGLE_NULL_RECT, Document::FRAMES,
                                  [=](Manager&, Document& document)
                                  {
                                    auto nulls = document.anm2.element_get(ElementType::NULLS);
                                    auto null = nulls ? element_child_id_get(*nulls, ElementType::NULL_ELEMENT, nullID)
                                                      : nullptr;
                                    if (!null) return;
                                    null->isShowRect = !null->isShowRect;
                                  });
              }
              overlay_icon(resources.icons[rectIcon].id, iconTintCurrent);
              ImGui::SetItemTooltip("%s", isShowRect ? localize.get(TOOLTIP_NULL_RECT_SHOWN)
                                                     : localize.get(TOOLTIP_NULL_RECT_HIDDEN));
            }
          }

          ImGui::PopStyleVar(2);
          ImGui::PopStyleColor(3);
        }
        else
        {
          auto cursorPos = ImGui::GetCursorPos();
          ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
          ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4());
          ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4());
          ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2());
          ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
          ImGui::SetCursorPos(
              ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x,
                     (itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));

          auto& isShowUnused = settings.timelineIsShowUnused;
          auto unusedIcon = isShowUnused ? icon::SHOW_UNUSED : icon::HIDE_UNUSED;
          if (ImGui::ImageButton("##Unused Toggle", resources.icons[unusedIcon].id, icon_size_get()))
            isShowUnused = !isShowUnused;
          overlay_icon(resources.icons[unusedIcon].id, iconTintCurrent);
          ImGui::SetItemTooltip("%s", isShowUnused ? localize.get(TOOLTIP_UNUSED_ITEMS_SHOWN)
                                                   : localize.get(TOOLTIP_UNUSED_ITEMS_HIDDEN));

          auto& showLayersOnly = settings.timelineIsOnlyShowLayers;
          auto layersIcon = showLayersOnly ? icon::SHOW_LAYERS : icon::HIDE_LAYERS;
          ImGui::SetCursorPos(
              ImVec2(itemSize.x - (ImGui::GetTextLineHeightWithSpacing() * 2) - ImGui::GetStyle().ItemSpacing.x,
                     (itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
          if (ImGui::ImageButton("##Layers Toggle", resources.icons[layersIcon].id, icon_size_get()))
            showLayersOnly = !showLayersOnly;
          overlay_icon(resources.icons[layersIcon].id, iconTintCurrent);
          ImGui::SetItemTooltip("%s", showLayersOnly ? localize.get(TOOLTIP_ONLY_LAYERS_VISIBLE)
                                                     : localize.get(TOOLTIP_ALL_ITEMS_VISIBLE));
          ImGui::PopStyleVar(2);
          ImGui::PopStyleColor(3);

          ImGui::SetCursorPos(cursorPos);

          ImGui::BeginDisabled();
          ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
          ImGui::Text("(?)");
          ImGui::PopStyleColor();
          auto tooltipShortcuts = std::vformat(
              localize.get(TOOLTIP_TIMELINE_SHORTCUTS),
              std::make_format_args(settings.shortcutMovePlayheadBack, settings.shortcutMovePlayheadForward,
                                    settings.shortcutShortenFrame, settings.shortcutExtendFrame,
                                    settings.shortcutPreviousFrame, settings.shortcutNextFrame,
                                    settings.shortcutPreviousItem, settings.shortcutNextItem));
          ImGui::SetItemTooltip("%s", tooltipShortcuts.c_str());
          ImGui::EndDisabled();
        }
      }
      ImGui::EndChild();
      ImGui::PopStyleColor();
      ImGui::PopStyleVar(2);

      ImGui::PopID();
    };

    auto items_child = [&]()
    {
      auto itemsChildSize = ImVec2(ITEM_CHILD_WIDTH, ImGui::GetContentRegionAvail().y);

      ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
      bool isItemsChildOpen = ImGui::BeginChild("##Items Child", itemsChildSize, ImGuiChildFlags_Borders);
      ImGui::PopStyleVar();
      if (isItemsChildOpen)
      {
        auto itemsListChildSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y -
                                                                               ImGui::GetTextLineHeightWithSpacing() -
                                                                               ImGui::GetStyle().ItemSpacing.y * 2);

        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
        if (ImGui::BeginChild("##Items List Child", itemsListChildSize, ImGuiChildFlags_Borders,
                              ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar))
        {
          if (animation && ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, ImGuiInputFlags_RouteFocused))
          {
            auto rowReferences = timeline_row_references_get();
            row_selection_clear();
            for (auto rowReference : rowReferences)
              row_selection_insert(rowReference);
            if (!rowReferences.empty())
            {
              rowSelectionAnchor = rowReferences.front();
              isRowSelectionAnchorSet = true;
            }
            frames_selection_reset_for(document);
          }

          if (animation && shortcut(manager.chords[SHORTCUT_GROUP], shortcut::FOCUSED) &&
              !item_references_groupable_get().empty())
            item_group();

          ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2());
          ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 0.0f);
          if (ImGui::BeginTable("##Item Table", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY))
          {
            ImGui::GetCurrentWindow()->Flags |= ImGuiWindowFlags_NoScrollWithMouse;
            ImGui::SetScrollY(scroll.y);

            ImGui::TableSetupScrollFreeze(0, 1);
            ImGui::TableSetupColumn("##Items");

            auto item_child_row = [&](const TimelineItemRow& row, int index)
            {
              ImGui::TableNextRow();
              ImGui::TableSetColumnIndex(0);
              item_child(row, index);
            };

            int index{};
            item_child_row({.type = NONE}, index++);
            if (animation)
              for (const auto& row : timeline_item_rows_get())
                item_child_row(row, index++);

            if (isHorizontalScroll && ImGui::GetCurrentWindow()->ScrollbarY)
            {
              ImGui::TableNextRow();
              ImGui::TableSetColumnIndex(0);
              ImGui::Dummy(ImVec2(0, style.ScrollbarSize));
            }

            ImGui::EndTable();
          }
          ImGui::PopStyleVar(2);

          item_context_menu();
        }
        ImGui::PopStyleVar(2);
        ImGui::EndChild();

        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);

        ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + style.WindowPadding.x, ImGui::GetCursorPosY()));
        auto widgetSize = widget_size_with_row_get(2, ImGui::GetContentRegionAvail().x - style.WindowPadding.x);

        ImGui::BeginDisabled(!animation);
        {
          shortcut(manager.chords[SHORTCUT_ADD]);
          if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) itemProperties.open();
          set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_ITEM), settings.shortcutAdd);
          ImGui::SameLine();

          auto selectedRows = selected_row_references_get();
          auto isRemoveAvailable = std::ranges::any_of(selectedRows, [](const TimelineRowReference& row)
          { return row.type == LAYER || row.type == NULL_; });
          ImGui::BeginDisabled(!isRemoveAvailable);
          shortcut(manager.chords[SHORTCUT_REMOVE]);
          if (ImGui::Button(localize.get(BASIC_REMOVE), widgetSize)) item_remove();
          set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_ITEMS), settings.shortcutRemove);
          ImGui::EndDisabled();
        }
        ImGui::EndDisabled();

        ImGui::PopStyleVar();
      }
      ImGui::EndChild();
    };

    int frameMoveDropType = NONE;
    int frameMoveDropItemID = -1;
    int frameMoveDropIndex = -1;
    bool isFrameMoveDropTarget = false;

    auto frame_move_drag_clear = [&]()
    {
      frameMoveDrag = {};
      frameSelectionLocked.clear();
    };

    auto frames_move_to = [&](int targetType, int targetID, int insertIndex)
    {
      if (!frameMoveDrag.isActive || !animation || frameMoveDrag.animationIndex != reference.animationIndex) return;
      if (targetType == TRIGGER) return;

      auto drag = frameMoveDrag;
      std::erase_if(drag.references, [](const Reference& frameReference)
                    { return frameReference.itemType == TRIGGER || frameReference.frameIndex < 0; });
      if (drag.references.empty() && drag.frameIndex >= 0)
        drag.references.push_back({drag.animationIndex, drag.type, drag.itemID, drag.frameIndex});
      std::erase_if(drag.references, [](const Reference& frameReference)
                    { return frameReference.itemType == TRIGGER || frameReference.frameIndex < 0; });
      if (drag.references.empty()) return;

      edit_command_push(EDIT_MOVE_FRAMES, Document::FRAMES,
                        [=, this](Manager&, Document& document) mutable
                        {
                          auto targetItem = command_item_get(document, drag.animationIndex, targetType, targetID);
                          if (!targetItem) return;

                          std::map<Reference, std::set<int>> groupedFrames{};
                          for (auto frameReference : drag.references)
                          {
                            auto itemReference = item_reference_from_frame_get(frameReference);
                            groupedFrames[itemReference].insert(frameReference.frameIndex);
                          }

                          int removedBeforeTarget = 0;
                          std::vector<Element> movedFrames;
                          for (auto& [itemReference, indices] : groupedFrames)
                          {
                            auto sourceItem = command_item_get(document, itemReference.animationIndex,
                                                               itemReference.itemType, itemReference.itemID);
                            if (!sourceItem) continue;

                            for (auto i : indices)
                            {
                              if (i < 0 || i >= (int)sourceItem->children.size()) continue;
                              movedFrames.push_back(std::move(sourceItem->children[i]));
                              if (itemReference.itemType == targetType && itemReference.itemID == targetID &&
                                  i < insertIndex)
                                ++removedBeforeTarget;
                            }

                            for (auto it = indices.rbegin(); it != indices.rend(); ++it)
                            {
                              auto i = *it;
                              if (i >= 0 && i < (int)sourceItem->children.size())
                                sourceItem->children.erase(sourceItem->children.begin() + i);
                            }
                          }

                          if (movedFrames.empty()) return;

                          int desired = std::clamp(insertIndex, 0, (int)targetItem->children.size());
                          desired -= removedBeforeTarget;
                          desired = std::clamp(desired, 0, (int)targetItem->children.size());

                          auto insertPosResult = desired;
                          auto insertedCount = (int)movedFrames.size();
                          targetItem->children.insert(targetItem->children.begin() + insertPosResult,
                                                      std::make_move_iterator(movedFrames.begin()),
                                                      std::make_move_iterator(movedFrames.end()));

                          if (insertedCount <= 0) return;

                          document.frames.selection.clear();
                          document.frames.references.clear();
                          for (int offset = 0; offset < insertedCount; ++offset)
                          {
                            document.frames.selection.insert(insertPosResult + offset);
                            document.frames.references.insert(
                                {drag.animationIndex, targetType, targetID, insertPosResult + offset});
                          }

                          document.reference = {drag.animationIndex, targetType, targetID, insertPosResult};
                          document.frameTime = frame_time_from_index_get(*targetItem, document.reference.frameIndex);
                          frameSelectionSnapshot.assign(document.frames.selection.begin(),
                                                        document.frames.selection.end());
                          frameSelectionSnapshotReference = document.reference;
                          frameSelectionLocked.clear();
                          isFrameSelectionLocked = false;
                          frameFocusIndex = document.reference.frameIndex;
                          frameFocusRequested = true;
                          if (targetType == LAYER)
                            if (auto layer = command_layer_get(document, targetID))
                              document.spritesheet.reference = layer->spritesheetId;
                        });
    };

    float playheadLineCenterX{};
    float playheadLineTopY{};
    bool isPlayheadLineSet{};
    ImVec2 frameBoxClipMin{};
    ImVec2 frameBoxClipMax{};
    bool isFrameBoxClipSet{};
    auto frame_box_content_point_get = [&]()
    {
      auto mousePos = ImGui::GetIO().MousePos;
      return ImVec2(mousePos.x + scroll.x - frameBoxClipMin.x, mousePos.y + scroll.y - frameBoxClipMin.y);
    };
    auto frame_box_screen_point_get = [&](ImVec2 point)
    { return ImVec2(point.x - scroll.x + frameBoxClipMin.x, point.y - scroll.y + frameBoxClipMin.y); };
    auto is_frame_box_overlapping = [](ImVec2 leftMin, ImVec2 leftMax, ImVec2 rightMin, ImVec2 rightMax)
    {
      return leftMin.x <= rightMax.x && leftMax.x >= rightMin.x && leftMin.y <= rightMax.y &&
             leftMax.y >= rightMin.y;
    };
    auto frame_overlay_draw = [&](ImDrawList* drawList, ImVec2 clipMin, ImVec2 clipMax)
    {
      if (isFrameBoxSelecting && isFrameBoxClipSet)
      {
        auto boxContentMin =
            ImVec2(std::min(frameBoxStart.x, frameBoxEnd.x), std::min(frameBoxStart.y, frameBoxEnd.y));
        auto boxContentMax =
            ImVec2(std::max(frameBoxStart.x, frameBoxEnd.x), std::max(frameBoxStart.y, frameBoxEnd.y));
        auto boxMin = frame_box_screen_point_get(boxContentMin);
        auto boxMax = frame_box_screen_point_get(boxContentMax);
        auto boxClipMin = clipMin;
        if (isPlayheadLineSet) boxClipMin.y = std::max(boxClipMin.y, playheadLineTopY);
        drawList->PushClipRect(boxClipMin, clipMax, true);
        drawList->AddRectFilled(boxMin, boxMax, ImGui::GetColorU32(ImGuiCol_DragDropTargetBg));
        drawList->AddRect(boxMin, boxMax, ImGui::GetColorU32(ImGuiCol_DragDropTarget));
        drawList->PopClipRect();
      }

      if (isPlayheadLineSet)
      {
        auto lineTopY = std::max(clipMin.y, playheadLineTopY);
        if (clipMax.y > lineTopY)
        {
          auto linePos = ImVec2(playheadLineCenterX - (PLAYHEAD_LINE_THICKNESS * 0.5f), lineTopY);
          auto lineSize = ImVec2(PLAYHEAD_LINE_THICKNESS * 0.5f, clipMax.y - lineTopY);
          drawList->PushClipRect(clipMin, clipMax, true);
          drawList->AddRectFilled(linePos, ImVec2(linePos.x + lineSize.x, linePos.y + lineSize.y),
                                  ImGui::GetColorU32(playheadLineColor));
          drawList->PopClipRect();
        }
      }
    };

    auto frame_child = [&](const TimelineItemRow& row, int& index, float width)
    {
      auto type = row.type;
      auto id = row.id;
      auto childSize = ImVec2(width, rowFrameChildHeight);
      if (row.isGroup)
      {
        auto group = row_group_get(row);
        if (!group) return;

        ImGui::PushID(index);
        ImGui::BeginChild("##Frames Group Child", childSize, ImGuiChildFlags_None,
                          ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar);
        ImGui::EndChild();
        index++;
        ImGui::PopID();
        return;
      }

      auto item = item_get(type, id);
      if (type != NONE && !item) return;

      auto isVisible = item ? item->isVisible && is_track_group_visible(type, row.groupId) : false;
      auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
      if (isOnlyShowLayers && type != LAYER) isVisible = false;

      auto colorVec = type_color_base_vec(type);
      auto colorActiveVec = type_color_active_vec(type);
      auto colorHoveredVec = type_color_hovered_vec(type);
      auto color = to_imvec4(colorVec);
      auto colorActive = to_imvec4(colorActiveVec);
      auto colorHovered = to_imvec4(colorHoveredVec);
      auto colorHidden = to_imvec4(colorVec * COLOR_HIDDEN_MULTIPLIER);
      auto colorActiveHidden = to_imvec4(colorActiveVec * COLOR_HIDDEN_MULTIPLIER);
      auto colorHoveredHidden = to_imvec4(colorHoveredVec * COLOR_HIDDEN_MULTIPLIER);

      ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
      ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);

      ImGui::PopStyleVar(2);

      ImGui::PushID(index);

      ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2());
      bool isDefaultChild = type == NONE;
      if (isLightTheme && isDefaultChild) ImGui::PushStyleColor(ImGuiCol_ChildBg, TIMELINE_CHILD_BG_COLOR_LIGHT);

      bool isFramesChildVisible = ImGui::BeginChild("##Frames Child", childSize, ImGuiChildFlags_Borders);
      auto drawList = ImGui::GetWindowDrawList();
      auto clipMax = drawList->GetClipRectMax();
      auto length = animation ? animation->frameNum : 0;
      auto frameSize = ImVec2(ImGui::GetTextLineHeight(), ImGui::GetContentRegionAvail().y);
      auto framesSize = ImVec2(frameSize.x * length, frameSize.y);
      auto cursorPos = ImGui::GetCursorPos();
      auto cursorScreenPos = ImGui::GetCursorScreenPos();
      auto border = glm::max(0.5f, ImGui::GetStyle().FrameBorderSize * 0.5f);
      auto borderLineLength = frameSize.y / 5;
      auto frameMin = std::max(0, (int)std::floor(scroll.x / frameSize.x) - 1);
      auto frameMax = std::min(FRAME_NUM_MAX, (int)std::ceil((scroll.x + clipMax.x) / frameSize.x) + 1);

      if (isFrameBoxSelecting && isFrameBoxClipSet && type != NONE && animation && item)
      {
        auto boxMin = ImVec2(std::min(frameBoxStart.x, frameBoxEnd.x), std::min(frameBoxStart.y, frameBoxEnd.y));
        auto boxMax = ImVec2(std::max(frameBoxStart.x, frameBoxEnd.x), std::max(frameBoxStart.y, frameBoxEnd.y));
        auto rowMinY = cursorScreenPos.y + scroll.y - frameBoxClipMin.y;
        auto rowMaxY = rowMinY + childSize.y;
        float selectionFrameTime{};
        for (auto [i, frame] : std::views::enumerate(item->children))
        {
          auto frameReference = Reference{reference.animationIndex, type, id, (int)i};
          auto frameStart = type == TRIGGER ? frame.atFrame : selectionFrameTime;
          auto frameEnd = type == TRIGGER ? frameStart + 1.0f : frameStart + frame.duration;
          auto frameContentMin = ImVec2(frameStart * frameSize.x, rowMinY);
          auto frameContentMax = ImVec2(frameEnd * frameSize.x, rowMaxY);
          if (is_frame_box_overlapping(frameContentMin, frameContentMax, boxMin, boxMax))
            frameBoxSelection.insert(frameReference);
          if (type != TRIGGER) selectionFrameTime += frame.duration;
        }
      }

      if (isFramesChildVisible)
      {
        if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
            ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused))
        {
          if (!frames.references.empty() || !frames.selection.empty())
          {
            reference.frameIndex = -1;
            frames_selection_reset_for(document);
          }
          else if (reference.itemType != NONE || reference.itemID != -1)
            reference_clear();
        }

        if (type == NONE)
        {
          if (length > 0)
          {
            if (isLightTheme)
            {
              auto totalMax = ImVec2(cursorScreenPos.x + framesSize.x, cursorScreenPos.y + framesSize.y);
              drawList->AddRectFilled(cursorScreenPos, totalMax, ImGui::GetColorU32(timelineBackgroundColor));
              float animationWidth = std::min(framesSize.x, frameSize.x * (float)length);
              drawList->AddRectFilled(cursorScreenPos,
                                      ImVec2(cursorScreenPos.x + animationWidth, cursorScreenPos.y + framesSize.y),
                                      ImGui::GetColorU32(timelinePlayheadRectColor));
            }
            else
            {
              drawList->AddRectFilled(cursorScreenPos,
                                      ImVec2(cursorScreenPos.x + framesSize.x, cursorScreenPos.y + framesSize.y),
                                      ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Header)));
            }
          }

          for (int i = frameMin; i < frameMax; i++)
          {
            auto frameScreenPos = ImVec2(cursorScreenPos.x + frameSize.x * (float)i, cursorScreenPos.y);

            drawList->AddRect(frameScreenPos, ImVec2(frameScreenPos.x + border, frameScreenPos.y + borderLineLength),
                              ImGui::GetColorU32(timelineTickColor), 0, 0, 0.5f);

            drawList->AddRect(ImVec2(frameScreenPos.x, frameScreenPos.y + frameSize.y - borderLineLength),
                              ImVec2(frameScreenPos.x + border, frameScreenPos.y + frameSize.y),
                              ImGui::GetColorU32(timelineTickColor), 0, 0, 0.5);

            if (i % FRAME_MULTIPLE == 0)
            {
              auto string = std::to_string(i);
              auto textSize = ImGui::CalcTextSize(string.c_str());
              auto textPos = ImVec2(frameScreenPos.x + (frameSize.x - textSize.x) / 2,
                                    frameScreenPos.y + (frameSize.y - textSize.y) / 2);

              drawList->AddRectFilled(frameScreenPos,
                                      ImVec2(frameScreenPos.x + frameSize.x, frameScreenPos.y + frameSize.y),
                                      ImGui::GetColorU32(frameMultipleOverlayColor));

              drawList->AddText(textPos, ImGui::GetColorU32(textMultipleColor), string.c_str());
            }
          }

          if (ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) &&
              ImGui::IsMouseClicked(ImGuiMouseButton_Left))
          {
            playback_stop();
            isDragging = true;
          }

          auto childPos = ImGui::GetWindowPos();
          auto mousePos = ImGui::GetIO().MousePos;
          auto localMousePos = ImVec2(mousePos.x - childPos.x, mousePos.y - childPos.y);
          hoveredTime = floorf(localMousePos.x / frameSize.x);

          if (isDragging)
          {
            playback.time = hoveredTime;
            playback.clamp(settings.playbackIsClamp ? length : FRAME_NUM_MAX);
            document.frameTime = playback.time;
          }

          if (!playback.isPlaying) playback.clamp(settings.playbackIsClamp ? length : FRAME_NUM_MAX);

          if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) isDragging = false;

          if (length > 0)
          {
            ImGui::SetCursorPos(ImVec2(cursorPos.x + frameSize.x * floorf(playback.time), cursorPos.y));
            ImGui::Image(resources.icons[icon::PLAYHEAD].id, frameSize);
            auto playheadMin = ImGui::GetItemRectMin();
            auto playheadMax = ImGui::GetItemRectMax();
            playheadLineCenterX = (playheadMin.x + playheadMax.x) * 0.5f;
            playheadLineTopY = playheadMax.y;
            isPlayheadLineSet = true;
            overlay_icon(resources.icons[icon::PLAYHEAD].id, playheadIconTint, true);
          }
        }
        else if (animation)
        {
          float frameTime{};

          for (int i = frameMin; i < frameMax; i++)
          {
            auto frameScreenPos = ImVec2(cursorScreenPos.x + frameSize.x * (float)i, cursorScreenPos.y);
            auto frameRectMax = ImVec2(frameScreenPos.x + frameSize.x, frameScreenPos.y + frameSize.y);

            drawList->AddRect(frameScreenPos, frameRectMax, ImGui::GetColorU32(frameBorderColor));

            if (i % FRAME_MULTIPLE == 0)
              drawList->AddRectFilled(frameScreenPos, frameRectMax, ImGui::GetColorU32(frameMultipleOverlayColor));
          }

          if (!frameMoveDrag.isActive && !ImGui::IsKeyDown(ImGuiMod_Shift) &&
              ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) &&
              ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGui::IsAnyItemHovered())
          {
            isFrameBoxPending = true;
            isFrameBoxAdditive = ImGui::IsKeyDown(ImGuiMod_Ctrl);
            frameBoxStart = frame_box_content_point_get();
            frameBoxEnd = frameBoxStart;
            frameBoxSelection.clear();
          }

          bool isFrameMovePreview = false;
          ImVec2 frameMovePreviewMin{};
          ImVec2 frameMovePreviewMax{};
          bool isFrameMoveHoveredFrame = false;
          ImVec2 frameMoveHoveredFrameMin{};
          ImVec2 frameMoveHoveredFrameMax{};

          if (frameMoveDrag.isActive && type != TRIGGER)
          {
            auto mousePos = ImGui::GetIO().MousePos;
            auto rowMin = cursorScreenPos;
            auto rowMax = ImVec2(cursorScreenPos.x + width, cursorScreenPos.y + frameSize.y);
            if (mousePos.x >= rowMin.x && mousePos.x < rowMax.x && mousePos.y >= rowMin.y && mousePos.y < rowMax.y)
            {
              auto mouseX = mousePos.x - cursorScreenPos.x;
              auto targetTime = glm::max(0.0f, mouseX / frameSize.x);
              int dropIndex = (int)item->children.size();
              float dropFrameTime{};
              float frameTime{};

              for (auto [i, frame] : std::views::enumerate(item->children))
              {
                auto frameStart = frameTime;
                auto frameEnd = frameStart + frame.duration;
                if (!isFrameMoveHoveredFrame && targetTime >= frameStart && targetTime < frameEnd)
                {
                  isFrameMoveHoveredFrame = true;
                  frameMoveHoveredFrameMin = ImVec2(cursorScreenPos.x + frameStart * frameSize.x, cursorScreenPos.y);
                  frameMoveHoveredFrameMax =
                      ImVec2(cursorScreenPos.x + frameEnd * frameSize.x, cursorScreenPos.y + frameSize.y);
                }

                auto midpoint = frameStart + ((float)frame.duration * 0.5f);
                if (targetTime < midpoint)
                {
                  dropIndex = (int)i;
                  dropFrameTime = frameStart;
                  break;
                }

                frameTime = frameEnd;
                dropFrameTime = frameTime;
              }

              frameMoveDropType = type;
              frameMoveDropItemID = id;
              frameMoveDropIndex = dropIndex;
              isFrameMoveDropTarget = true;

              auto dropX = cursorScreenPos.x + dropFrameTime * frameSize.x;
              auto previewWidth = glm::max(frameSize.x, (float)frameMoveDrag.duration * frameSize.x);
              frameMovePreviewMin = ImVec2(dropX, cursorScreenPos.y);
              frameMovePreviewMax = ImVec2(dropX + previewWidth, cursorScreenPos.y + frameSize.y);
              isFrameMovePreview = true;
            }
          }

          for (auto [i, frame] : std::views::enumerate(item->children))
          {
            ImGui::PushID((int)i);

            auto frameReference = Reference{reference.animationIndex, type, id, (int)i};
            auto isFrameVisible = isVisible && frame.isVisible;
            auto isReferenced = reference == frameReference;
            auto isSelected = frames.references.contains(frameReference) ||
                              (frames.references.empty() && frames.selection.contains((int)i) &&
                               reference.itemType == type && reference.itemID == id);

            if (type == TRIGGER) frameTime = frame.atFrame;

            auto buttonSize = type == TRIGGER ? frameSize : to_imvec2(vec2(frameSize.x * frame.duration, frameSize.y));
            auto frameStart = type == TRIGGER ? frame.atFrame : frameTime;
            auto frameEnd = type == TRIGGER ? frameStart + 1.0f : frameStart + frame.duration;
            if (frameEnd <= (float)frameMin || frameStart >= (float)frameMax)
            {
              if (type != TRIGGER) frameTime += frame.duration;
              ImGui::PopID();
              continue;
            }
            auto buttonPos = ImVec2(cursorPos.x + (frameTime * frameSize.x), cursorPos.y);

            if (frameFocusRequested && frameFocusIndex == (int)i && reference == frameReference)
              frameFocusRequested = false;

            ImGui::SetCursorPos(buttonPos);

            auto buttonScreenPos = ImGui::GetCursorScreenPos();
            auto fillColor = isSelected ? (isFrameVisible ? colorActive : colorActiveHidden)
                                        : (isFrameVisible ? color : colorHidden);
            drawList->AddRectFilled(buttonScreenPos,
                                    ImVec2(buttonScreenPos.x + buttonSize.x, buttonScreenPos.y + buttonSize.y),
                                    ImGui::GetColorU32(fillColor), FRAME_ROUNDING);

            ImGui::PushStyleColor(ImGuiCol_Header, isFrameVisible ? colorActive : colorActiveHidden);
            ImGui::PushStyleColor(ImGuiCol_HeaderActive, isFrameVisible ? colorActive : colorActiveHidden);
            ImGui::PushStyleColor(ImGuiCol_HeaderHovered, isFrameVisible ? colorHovered : colorHoveredHidden);
            ImGui::PushStyleColor(ImGuiCol_NavCursor, isFrameVisible ? colorHovered : colorHoveredHidden);

            ImGui::SetNextItemAllowOverlap();
            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, FRAME_ROUNDING);
            ImGui::SetNextItemSelectionUserData((int)i);
            bool isDifferentItem = reference.itemType != type || reference.itemID != id;
            if (ImGui::Selectable("##Frame Button", isSelected, ImGuiSelectableFlags_None, buttonSize))
            {
              if (type == LAYER)
                if (auto layer = layer_get(id)) document.spritesheet.reference = layer->spritesheetId;

              if (type != TRIGGER)
              {
                if (ImGui::IsKeyDown(ImGuiMod_Alt))
                {
                  auto targetReference = frameReference;
                  edit_command_push(EDIT_FRAME_INTERPOLATION, Document::FRAMES,
                                    [=](Manager&, Document& document)
                                    {
                                      auto frame = command_frame_get(document, targetReference);
                                      if (!frame) return;
                                      frame->interpolation = frame->interpolation == Interpolation::NONE
                                                                 ? Interpolation::LINEAR
                                                                 : Interpolation::NONE;
                                    });
                }

                document.frameTime = frameTime;
              }

              auto isCtrlDown = ImGui::IsKeyDown(ImGuiMod_Ctrl);
              auto isShiftDown = ImGui::IsKeyDown(ImGuiMod_Shift);
              if (isShiftDown)
              {
                auto isHadAnchor = isFrameSelectionAnchorSet;
                auto anchorReference = isHadAnchor ? frameSelectionAnchor : frameReference;
                auto isRangeSelected =
                    frame_selection_range_set_for(document, anchorReference, frameReference, isCtrlDown);
                if (!isRangeSelected) frame_selection_set_for(document, frameReference);
                if (!isHadAnchor || !isRangeSelected) frameSelectionAnchor = frameReference;
                isFrameSelectionAnchorSet = true;
              }
              else if (isCtrlDown)
              {
                frame_selection_toggle_for(document, frameReference);
                frameSelectionAnchor = frameReference;
                isFrameSelectionAnchorSet = true;
              }
              else
              {
                frame_selection_set_for(document, frameReference);
                frameSelectionAnchor = frameReference;
                isFrameSelectionAnchorSet = true;
              }
              reference = frameReference;
              isReferenced = true;
              region.reference = -1;
              region.selection.clear();
              if (isDifferentItem) frames_selection_sync_for(document);
            }
            ImGui::PopStyleVar();

            ImGui::PopStyleColor(4);

            if (!isDraggedFrameActive && ImGui::IsItemHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Left))
            {
              if (ImGui::IsMouseDown(ImGuiMouseButton_Left))
              {
                if (type == TRIGGER || ImGui::IsKeyDown(ImGuiMod_Ctrl))
                {
                  isDraggedFrameActive = true;
                  draggedFrameReference = frameReference;
                  draggedFrameType = type;
                  draggedFrameIndex = (int)i;
                  draggedFrameStart = hoveredTime;
                  if (type != TRIGGER) draggedFrameStartDuration = frame.duration;
                  draggedFrameStartMouseX = ImGui::GetIO().MousePos.x;
                  draggedFrameWidth = frameSize.x;
                }
              }
            }

            if (type != TRIGGER)
            {
              if (!isDraggedFrameActive && !frameMoveDrag.isActive && ImGui::IsItemActive() &&
                  ImGui::IsMouseDragging(ImGuiMouseButton_Left))
              {
                auto selectedReferences = frame_references_for_current_get();
                if (!selectedReferences.contains(frameReference)) selectedReferences = {frameReference};
                std::erase_if(selectedReferences, [](const Reference& selectedReference)
                              { return selectedReference.itemType == TRIGGER; });
                if (selectedReferences.empty()) selectedReferences = {frameReference};
                int dragDuration = 0;
                for (auto selectedReference : selectedReferences)
                {
                  auto selectedItem = item_get(selectedReference.itemType, selectedReference.itemID);
                  auto selectedFrame =
                      selectedItem ? track_frame_get(*selectedItem, selectedReference.frameIndex) : nullptr;
                  if (selectedFrame) dragDuration += selectedFrame->duration;
                }
                dragDuration = glm::max(1, dragDuration);

                frameMoveDrag = {
                    .type = type,
                    .itemID = id,
                    .animationIndex = reference.animationIndex,
                    .frameIndex = (int)i,
                    .duration = dragDuration,
                    .indices = {},
                    .references = {selectedReferences.begin(), selectedReferences.end()},
                    .isActive = true,
                };
              }
            }

            auto rectMin = ImGui::GetItemRectMin();
            auto rectMax = ImGui::GetItemRectMax();
            auto borderColor = isReferenced ? frameBorderColorReferenced : frameBorderColor;
            auto borderThickness = isReferenced ? FRAME_BORDER_THICKNESS_REFERENCED : FRAME_BORDER_THICKNESS;
            drawList->AddRect(rectMin, rectMax, ImGui::GetColorU32(borderColor), FRAME_ROUNDING, 0, borderThickness);

            auto icon = icon::UNINTERPOLATED;
            if (type == TRIGGER)
              icon = icon::TRIGGER;
            else
            {
              switch (frame.interpolation)
              {
                case Interpolation::NONE:
                  icon = icon::UNINTERPOLATED;
                  break;
                case Interpolation::LINEAR:
                  icon = icon::INTERPOLATED;
                  break;
                case Interpolation::EASE_IN:
                  icon = icon::EASE_IN;
                  break;
                case Interpolation::EASE_OUT:
                  icon = icon::EASE_OUT;
                  break;
                case Interpolation::EASE_IN_OUT:
                  icon = icon::EASE_IN_OUT;
                  break;
                default:
                  icon = icon::UNINTERPOLATED;
                  break;
              }
            }
            auto iconPos = ImVec2(cursorPos.x + (frameTime * frameSize.x),
                                  cursorPos.y + (frameSize.y / 2) - (icon_size_get().y / 2));
            ImGui::SetCursorPos(iconPos);
            ImGui::Image(resources.icons[icon].id, icon_size_get());
            overlay_icon(resources.icons[icon].id, iconTintDefault);

            if (type != TRIGGER) frameTime += frame.duration;

            ImGui::PopID();
          }

          if (isFrameMovePreview)
          {
            drawList->AddRectFilled(frameMovePreviewMin, frameMovePreviewMax,
                                    ImGui::GetColorU32(ImGuiCol_DragDropTargetBg), FRAME_ROUNDING);
            drawList->AddRect(frameMovePreviewMin, frameMovePreviewMax, ImGui::GetColorU32(ImGuiCol_DragDropTarget),
                              FRAME_ROUNDING, 0, ImGui::GetStyle().DragDropTargetBorderSize);
          }

          if (isFrameMoveHoveredFrame)
            drawList->AddRect(frameMoveHoveredFrameMin, frameMoveHoveredFrameMax,
                              ImGui::GetColorU32(ImGuiCol_DragDropTarget), FRAME_ROUNDING, 0,
                              ImGui::GetStyle().DragDropTargetBorderSize * 1.5f);

          if (!isFrameBoxSelecting && !frameMoveDrag.isActive && !isDraggedFrameActive && ImGui::IsWindowHovered() &&
              (ImGui::IsMouseReleased(ImGuiMouseButton_Left) || ImGui::IsMouseReleased(ImGuiMouseButton_Right)) &&
              !ImGui::IsAnyItemHovered())
            row_selection_set(row);

          if (isFrameSelectionLocked)
          {
            frames.selection.clear();
            frames.references.clear();
            for (int idx : frameSelectionLocked)
            {
              frames.selection.insert(idx);
              frames.references.insert({reference.animationIndex, type, id, idx});
            }
            isFrameSelectionLocked = false;
            frameSelectionLocked.clear();
          }
          if (reference.itemType == type && reference.itemID == id)
          {
            frameSelectionSnapshot.assign(frames.selection.begin(), frames.selection.end());
            frameSelectionSnapshotReference = reference;
          }
        }

      }

      if (isDraggedFrameActive)
      {
        ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
        auto durationDelta = draggedFrameWidth > 0.0f
                                 ? static_cast<int>((ImGui::GetIO().MousePos.x - draggedFrameStartMouseX) /
                                                    draggedFrameWidth)
                                 : hoveredTime - draggedFrameStart;
        auto isDraggedFrameChanged =
            draggedFrameType == TRIGGER ? hoveredTime != draggedFrameStart : durationDelta != 0;

        if (!isDraggedFrameSnapshot && isDraggedFrameChanged)
        {
          isDraggedFrameSnapshot = true;
          snapshot_command_push(draggedFrameType == TRIGGER ? EDIT_TRIGGER_AT_FRAME : EDIT_FRAME_DURATION);
        }

        if (isDraggedFrameSnapshot)
        {
          auto targetReference = draggedFrameReference;
          auto targetType = draggedFrameType;
          auto targetIndex = draggedFrameIndex;
          auto targetStartDuration = draggedFrameStartDuration;
          auto targetHoveredTime = hoveredTime;
          auto targetDurationDelta = durationDelta;
          auto isPlaybackClamp = settings.playbackIsClamp;
          auto animationLength = animation ? animation->frameNum : FRAME_NUM_MAX;
          command_push([=](Manager&, Document& document)
                       {
                         auto item = command_item_get(document, targetReference.animationIndex,
                                                      targetReference.itemType, targetReference.itemID);
                         auto frame = command_frame_get(document, targetReference);
                         if (!item || !frame) return;

                         if (targetType == TRIGGER)
                         {
                           frame->atFrame =
                               glm::clamp(targetHoveredTime, 0,
                                          isPlaybackClamp ? animationLength - 1 : FRAME_NUM_MAX - 1);

                           for (auto [i, trigger] : std::views::enumerate(item->children))
                           {
                             if ((int)i == targetIndex) continue;
                             if (trigger.atFrame == frame->atFrame) frame->atFrame--;
                           }
                         }
                         else
                         {
                           frame->duration = glm::clamp(targetStartDuration + targetDurationDelta, FRAME_DURATION_MIN,
                                                        FRAME_DURATION_MAX);
                         }
                       });
        }

        if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))
        {
          auto targetReference = draggedFrameReference;
          auto targetType = draggedFrameType;
          command_push([=](Manager&, Document& document)
                       {
                         auto item = command_item_get(document, targetReference.animationIndex,
                                                      targetReference.itemType, targetReference.itemID);
                         if (targetType == TRIGGER && item) frames_sort_by_at_frame(*item);
                         document.anm2_change(Document::FRAMES);
                       });
          isDraggedFrameActive = false;
          draggedFrameReference = {};
          draggedFrameType = NONE;
          draggedFrameIndex = -1;
          draggedFrameStart = -1;
          draggedFrameStartDuration = -1;
          draggedFrameStartMouseX = 0.0f;
          draggedFrameWidth = 0.0f;
          isDraggedFrameSnapshot = false;
          frameSelectionLocked.clear();
        }
      }

      context_menu();

      ImGui::EndChild();
      if (isLightTheme && isDefaultChild) ImGui::PopStyleColor();
      ImGui::PopStyleVar();

      index++;
      ImGui::PopID();
    };

    auto frames_child = [&]()
    {
      auto cursorPos = ImGui::GetCursorPos();
      ImGui::SetCursorPos(ImVec2(cursorPos.x + ITEM_CHILD_WIDTH, cursorPos.y));

      auto framesChildSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y);

      if (ImGui::BeginChild("##Frames Child", framesChildSize, ImGuiChildFlags_Borders))
      {
        auto viewListChildSize =
            ImVec2(ImGui::GetContentRegionAvail().x,
                   ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeightWithSpacing() - style.ItemSpacing.y * 2);

        auto animationsLength = [&]()
        {
          int length{};
          if (auto animations = anm2.element_get(ElementType::ANIMATIONS))
            for (auto& item : animations->children)
              if (item.type == ElementType::ANIMATION) length = std::max(length, animation_length_get(item));
          return length;
        }();
        auto childWidth = animationsLength * ImGui::GetTextLineHeight();
        if (animation && animation->frameNum > animationsLength)
          childWidth = animation->frameNum * ImGui::GetTextLineHeight();
        childWidth = std::max(childWidth, ImGui::GetContentRegionAvail().x);

        childWidth *= WIDTH_MULTIPLIER;

        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
        if (ImGui::BeginChild("##Frames List Child", viewListChildSize, true, ImGuiWindowFlags_HorizontalScrollbar))
        {
          playheadLineCenterX = 0.0f;
          playheadLineTopY = 0.0f;
          isPlayheadLineSet = false;
          isFrameBoxClipSet = false;
          if (animation && ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, ImGuiInputFlags_RouteFocused))
          {
            group_selection_reset_for(document);
            document.frames.references = all_frame_references_for_items_get();
            if (!document.frames.references.empty())
            {
              reference = *document.frames.references.begin();
              frames_selection_sync_for(document);
            }
          }

          ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2());
          if (ImGui::BeginTable("##Frames List Table", 1,
                                ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY))
          {
            ImGuiWindow* window = ImGui::GetCurrentWindow();
            window->Flags |= ImGuiWindowFlags_NoScrollWithMouse;

            scroll.x = ImGui::GetScrollX();
            scroll.y = ImGui::GetScrollY();

            isHorizontalScroll = window->ScrollbarX;

            if (isWindowHovered)
            {
              auto& io = ImGui::GetIO();
              auto lineHeight = ImGui::GetTextLineHeightWithSpacing() * 2;

              scroll.x -= io.MouseWheelH * lineHeight;
              scroll.y -= io.MouseWheel * lineHeight;
            }

            ImGui::SetScrollX(scroll.x);
            ImGui::SetScrollY(scroll.y);

            auto frameBoxDrawList = ImGui::GetWindowDrawList();
            frameBoxClipMin = frameBoxDrawList->GetClipRectMin();
            frameBoxClipMax = frameBoxDrawList->GetClipRectMax();
            isFrameBoxClipSet = true;
            if (isFrameBoxPending || isFrameBoxSelecting)
            {
              auto& io = ImGui::GetIO();
              auto threshold = ImGui::GetTextLineHeightWithSpacing() * 0.125f;
              frameBoxEnd = frame_box_content_point_get();
              auto distance = ImVec2(frameBoxEnd.x - frameBoxStart.x, frameBoxEnd.y - frameBoxStart.y);
              if (isFrameBoxPending && distance.x * distance.x + distance.y * distance.y >= threshold * threshold)
              {
                isFrameBoxPending = false;
                isFrameBoxSelecting = true;
              }
              auto edgeSize = ImGui::GetTextLineHeightWithSpacing();
              auto scrollStep = edgeSize * 0.5f;
              if (isFrameBoxSelecting)
              {
                if (io.MousePos.x < frameBoxClipMin.x + edgeSize)
                  scroll.x -= scrollStep;
                else if (io.MousePos.x > frameBoxClipMax.x - edgeSize)
                  scroll.x += scrollStep;
                if (io.MousePos.y < frameBoxClipMin.y + edgeSize)
                  scroll.y -= scrollStep;
                else if (io.MousePos.y > frameBoxClipMax.y - edgeSize)
                  scroll.y += scrollStep;
                ImGui::SetScrollX(scroll.x);
                ImGui::SetScrollY(scroll.y);
                frameBoxEnd = frame_box_content_point_get();
                frameBoxSelection.clear();
              }
            }

            int index{};

            ImGui::TableSetupScrollFreeze(0, 1);
            ImGui::TableSetupColumn("##Frames");

            auto frames_child_row = [&](const TimelineItemRow& row)
            {
              ImGui::TableNextRow();
              ImGui::TableSetColumnIndex(0);
              frame_child(row, index, childWidth);
            };

            frames_child_row({.type = NONE});

            if (animation)
            {
              ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);

              for (const auto& row : timeline_item_rows_get())
                frames_child_row(row);

              ImGui::PopStyleVar();
            }
            ImGui::EndTable();
          }

          if (isFrameBoxClipSet)
          {
            auto overlayCursor = ImGui::GetCursorScreenPos();
            auto overlayPos = ImGui::GetWindowPos();
            auto overlaySize = ImGui::GetWindowSize();
            ImGui::SetCursorScreenPos(overlayPos);
            if (ImGui::BeginChild("##Frames Overlay Child", overlaySize, ImGuiChildFlags_None,
                                  ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground |
                                      ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse |
                                      ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoNavInputs))
              frame_overlay_draw(ImGui::GetWindowDrawList(), frameBoxClipMin, frameBoxClipMax);
            ImGui::EndChild();
            ImGui::SetCursorScreenPos(overlayCursor);
          }

          if (isFrameBoxPending || isFrameBoxSelecting)
          {
            if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))
            {
              if (isFrameBoxSelecting)
              {
                group_selection_reset_for(document);
                if (isFrameBoxAdditive)
                  document.frames.references.insert(frameBoxSelection.begin(), frameBoxSelection.end());
                else
                  document.frames.references = frameBoxSelection;
                document.items.references.clear();
                for (auto frameReference : document.frames.references)
                  document.items.references.insert(item_reference_from_frame_get(frameReference));
                if (!document.frames.references.empty()) reference = *document.frames.references.begin();
                frames_selection_sync_for(document);
              }
              isFrameBoxPending = false;
              isFrameBoxSelecting = false;
              frameBoxSelection.clear();
            }
          }

          ImGui::PopStyleVar();
        }
        ImGui::EndChild();
        ImGui::PopStyleVar();

        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);

        ImGui::SetCursorPos(
            ImVec2(ImGui::GetStyle().WindowPadding.x, ImGui::GetCursorPos().y + ImGui::GetStyle().ItemSpacing.y));

        auto widgetSize = widget_size_with_row_get(10);

        ImGui::BeginDisabled(!animation);
        {
          auto label = playback.isPlaying ? localize.get(LABEL_PAUSE) : localize.get(LABEL_PLAY);
          auto tooltip =
              playback.isPlaying ? localize.get(TOOLTIP_PAUSE_ANIMATION) : localize.get(TOOLTIP_PLAY_ANIMATION);

          shortcut(manager.chords[SHORTCUT_PLAY_PAUSE]);
          if (ImGui::Button(label, widgetSize)) playback.toggle();
          set_item_tooltip_shortcut(tooltip, settings.shortcutPlayPause);

          ImGui::SameLine();

          auto item = selected_item_get();
          auto selectedFrames = frame_references_for_current_get();
          auto selectedBakeFrames = selectedFrames;
          std::erase_if(selectedBakeFrames, [](const Reference& frameReference)
                        { return frameReference.itemType == TRIGGER; });

          ImGui::BeginDisabled(!item);
          {
            shortcut(manager.chords[SHORTCUT_INSERT_FRAME]);
            if (ImGui::Button(localize.get(LABEL_INSERT), widgetSize)) frame_insert();
            set_item_tooltip_shortcut(localize.get(TOOLTIP_INSERT_FRAME), settings.shortcutInsertFrame);

            ImGui::SameLine();

            ImGui::BeginDisabled(selectedFrames.empty());
            {
              shortcut(manager.chords[SHORTCUT_REMOVE]);
              if (ImGui::Button(localize.get(LABEL_DELETE), widgetSize)) frames_delete_action();
              set_item_tooltip_shortcut(localize.get(TOOLTIP_DELETE_FRAMES), settings.shortcutRemove);

              ImGui::SameLine();

              ImGui::BeginDisabled(selectedBakeFrames.empty());
              if (ImGui::Button(localize.get(LABEL_BAKE), widgetSize)) bakePopup.open();
              set_item_tooltip_shortcut(localize.get(TOOLTIP_BAKE_FRAMES), settings.shortcutBake);
              ImGui::EndDisabled();
            }
            ImGui::EndDisabled();
          }
          ImGui::EndDisabled();

          ImGui::SameLine();

          ImGui::BeginDisabled(!animation || animation->frameNum == animation_length_get(*animation));
          shortcut(manager.chords[SHORTCUT_FIT]);
          if (ImGui::Button(localize.get(LABEL_FIT_ANIMATION_LENGTH), widgetSize)) fit_animation_length();
          set_item_tooltip_shortcut(localize.get(TOOLTIP_FIT_ANIMATION_LENGTH), settings.shortcutFit);
          ImGui::EndDisabled();

          ImGui::SameLine();

          auto frameNum = animation ? animation->frameNum : dummy_value<int>();
          ImGui::SetNextItemWidth(widgetSize.x);
          if (input_int_range(localize.get(LABEL_ANIMATION_LENGTH), frameNum, FRAME_NUM_MIN, FRAME_NUM_MAX, STEP,
                              STEP_FAST, !animation ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0) &&
              animation)
          {
            auto animationIndex = reference.animationIndex;
            edit_command_push(EDIT_ANIMATION_LENGTH, Document::ANIMATIONS,
                              [=](Manager&, Document& document)
                              {
                                auto animation = command_animation_get(document, animationIndex);
                                if (!animation) return;
                                animation->frameNum = frameNum;
                              });
          }
          ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ANIMATION_LENGTH));

          ImGui::SameLine();

          auto isLoop = animation ? animation->isLoop : dummy_value<bool>();
          ImGui::SetNextItemWidth(widgetSize.x);
          if (ImGui::Checkbox(localize.get(LABEL_LOOP), &isLoop) && animation)
          {
            auto animationIndex = reference.animationIndex;
            edit_command_push(EDIT_LOOP, Document::ANIMATIONS,
                              [=](Manager&, Document& document)
                              {
                                auto animation = command_animation_get(document, animationIndex);
                                if (!animation) return;
                                animation->isLoop = isLoop;
                              });
          }
          ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_LOOP_ANIMATION));
        }
        ImGui::EndDisabled();

        ImGui::SameLine();

        auto info = info_get();
        auto fps = info ? info->fps : 30;
        ImGui::SetNextItemWidth(widgetSize.x);
        if (input_int_range(localize.get(LABEL_FPS), fps, FPS_MIN, FPS_MAX))
        {
          edit_command_push(EDIT_FPS, Document::INFO,
                            [=](Manager&, Document& document)
                            {
                              auto info = command_info_get(document);
                              if (!info)
                              {
                                document.anm2.root.children.push_back(element_make(ElementType::INFO));
                                info = &document.anm2.root.children.back();
                              }
                              info->fps = fps;
                            });
        }
        ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FPS));

        ImGui::SameLine();

        info = info_get();
        auto createdBy = info ? info->createdBy : std::string{};
        ImGui::SetNextItemWidth(widgetSize.x);
        if (input_text_string(localize.get(LABEL_AUTHOR), &createdBy))
        {
          edit_command_push(EDIT_AUTHOR, Document::INFO,
                            [=](Manager&, Document& document)
                            {
                              auto info = command_info_get(document);
                              if (!info)
                              {
                                document.anm2.root.children.push_back(element_make(ElementType::INFO));
                                info = &document.anm2.root.children.back();
                              }
                              info->createdBy = createdBy;
                            });
        }
        ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_AUTHOR));

        ImGui::SameLine();

        ImGui::SetNextItemWidth(widgetSize.x);
        ImGui::Checkbox(localize.get(LABEL_SOUND), &settings.timelineIsSound);
        ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SOUND));

        ImGui::PopStyleVar();
      }
      ImGui::EndChild();

      ImGui::SetCursorPos(cursorPos);
    };

    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
    if (ImGui::Begin(localize.get(LABEL_TIMELINE_WINDOW), &settings.windowIsTimeline))
    {
      isWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows |
                                               ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
      frames_child();
      if (frameMoveDrag.isActive && ImGui::IsMouseReleased(ImGuiMouseButton_Left))
      {
        if (isFrameMoveDropTarget) frames_move_to(frameMoveDropType, frameMoveDropItemID, frameMoveDropIndex);
        frame_move_drag_clear();
      }
      items_child();
    }
    ImGui::PopStyleVar();
    ImGui::End();

    if (itemProperties.update(manager, settings, document, reference)) group_selection_reset_for(document);
    group_properties_update();

    bakePopup.trigger();

    if (ImGui::BeginPopupModal(bakePopup.label(), &bakePopup.isOpen, ImGuiWindowFlags_NoResize))
    {
      auto& interval = settings.bakeInterval;
      auto& isRoundRotation = settings.bakeIsRoundRotation;
      auto& isRoundScale = settings.bakeIsRoundScale;

      auto frame = frame_get();

      input_int_range(localize.get(LABEL_INTERVAL), interval, FRAME_DURATION_MIN,
                      frame ? frame->duration : FRAME_DURATION_MIN);
      ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_INTERVAL));

      ImGui::Checkbox(localize.get(LABEL_ROUND_ROTATION), &isRoundRotation);
      ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ROUND_ROTATION));

      ImGui::Checkbox(localize.get(LABEL_ROUND_SCALE), &isRoundScale);
      ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ROUND_SCALE));

      auto widgetSize = widget_size_with_row_get(2);

      shortcut(manager.chords[SHORTCUT_CONFIRM]);
      if (ImGui::Button(localize.get(LABEL_BAKE), widgetSize))
      {
        frames_bake();
        bakePopup.close();
      }
      ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_BAKE_FRAMES_OPTIONS));

      ImGui::SameLine();

      shortcut(manager.chords[SHORTCUT_CANCEL]);
      if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) bakePopup.close();
      ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CANCEL_BAKE_FRAMES));

      ImGui::EndPopup();
    }

    if (animation)
    {
      if (shortcut(manager.chords[SHORTCUT_PLAY_PAUSE], shortcut::GLOBAL)) playback.toggle();

      if (shortcut(manager.chords[SHORTCUT_MOVE_PLAYHEAD_BACK], shortcut::GLOBAL))
      {
        playback_stop();
        playback.decrement(settings.playbackIsClamp ? animation->frameNum : FRAME_NUM_MAX);
        document.frameTime = playback.time;
      }

      if (shortcut(manager.chords[SHORTCUT_MOVE_PLAYHEAD_FORWARD], shortcut::GLOBAL))
      {
        playback_stop();
        playback.increment(settings.playbackIsClamp ? animation->frameNum : FRAME_NUM_MAX);
        document.frameTime = playback.time;
      }

      static bool isShortenChordHeld = false;
      auto isShortenFrame = shortcut(manager.chords[SHORTCUT_SHORTEN_FRAME], shortcut::GLOBAL);

      if (isShortenFrame && !isShortenChordHeld) snapshot_command_push(EDIT_SHORTEN_FRAME);
      if (isShortenFrame)
      {

        auto selectedFrames = frame_references_for_current_get();
        std::erase_if(selectedFrames,
                      [](const Reference& frameReference) { return frameReference.itemType == TRIGGER; });
        if (!selectedFrames.empty())
        {
          command_push([=](Manager&, Document& document)
                       {
                         for (auto frameReference : selectedFrames)
                         {
                           auto frame = command_frame_get(document, frameReference);
                           if (!frame) continue;
                           frame->duration = std::max(FRAME_DURATION_MIN, frame->duration - 1);
                         }
                         document.anm2_change(Document::FRAMES);
                       });
        }
      }
      isShortenChordHeld = isShortenFrame;

      static bool isExtendChordHeld = false;
      auto isExtendFrame = shortcut(manager.chords[SHORTCUT_EXTEND_FRAME], shortcut::GLOBAL);
      if (isExtendFrame && !isExtendChordHeld) snapshot_command_push(EDIT_EXTEND_FRAME);
      if (isExtendFrame)
      {

        auto selectedFrames = frame_references_for_current_get();
        std::erase_if(selectedFrames,
                      [](const Reference& frameReference) { return frameReference.itemType == TRIGGER; });
        if (!selectedFrames.empty())
        {
          command_push([=](Manager&, Document& document)
                       {
                         for (auto frameReference : selectedFrames)
                         {
                           auto frame = command_frame_get(document, frameReference);
                           if (!frame) continue;
                           frame->duration = std::min(FRAME_DURATION_MAX, frame->duration + 1);
                         }
                         document.anm2_change(Document::FRAMES);
                       });
        }
      }
      isExtendChordHeld = isExtendFrame;

      auto isPreviousFrame = shortcut(manager.chords[SHORTCUT_PREVIOUS_FRAME], shortcut::GLOBAL);
      auto isNextFrame = shortcut(manager.chords[SHORTCUT_NEXT_FRAME], shortcut::GLOBAL);
      auto isPreviousItem = shortcut(manager.chords[SHORTCUT_PREVIOUS_ITEM], shortcut::GLOBAL);
      auto isNextItem = shortcut(manager.chords[SHORTCUT_NEXT_ITEM], shortcut::GLOBAL);

      if (isPreviousFrame)
        if (auto item = selected_item_get(); item && !item->children.empty())
          reference.frameIndex = glm::clamp(--reference.frameIndex, 0, (int)item->children.size() - 1);

      if (isNextFrame)
        if (auto item = selected_item_get(); item && !item->children.empty())
          reference.frameIndex = glm::clamp(++reference.frameIndex, 0, (int)item->children.size() - 1);

      if (isPreviousFrame || isNextFrame)
      {
        if (auto item = selected_item_get(); item && !item->children.empty())
        {
          frames_selection_set_reference();
          document.frameTime = frame_time_from_index_get(*item, reference.frameIndex);
        }
      }

      if (isPreviousItem) reference_set_adjacent_item(-1);
      if (isNextItem) reference_set_adjacent_item(1);
    }

    if (isTextPushed) ImGui::PopStyleColor();
  }
}
