/*
 * Copyright (c) Contributors to the Open 3D Engine Project.
 * For complete copyright and license terms please see the LICENSE at the root of this distribution.
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 *
 */


#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>

#include <AzFramework/AzFrameworkNativeUIModule.h>
#include <AzFramework/Components/NativeUISystemComponent.h>
#include <AzFramework/StringFunc/StringFunc.h>

#include <AzToolsFramework/ActionManager/ActionManagerSystemComponent.h>
#include <AzToolsFramework/API/ComponentEntityObjectBus.h>
#include <AzToolsFramework/API/EditorCameraBus.h>
#include <AzToolsFramework/API/ViewPaneOptions.h>
#include <AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/AzToolsFrameworkModule.h>
#include <AzToolsFramework/Archive/ArchiveComponent.h>
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/AssetBundle/AssetBundleComponent.h>
#include <AzToolsFramework/AssetEditor/AssetEditorBus.h>
#include <AzToolsFramework/AssetEditor/AssetEditorWidget.h>
#include <AzToolsFramework/Commands/SelectionCommand.h>
#include <AzToolsFramework/Component/EditorComponentAPIComponent.h>
#include <AzToolsFramework/Component/EditorLevelComponentAPIComponent.h>
#include <AzToolsFramework/ComponentMode/ComponentModeDelegate.h>
#include <AzToolsFramework/ComponentModes/BoxComponentMode.h>
#include <AzToolsFramework/ComponentModes/CapsuleComponentMode.h>
#include <AzToolsFramework/ComponentModes/CylinderComponentMode.h>
#include <AzToolsFramework/ComponentModes/SphereComponentMode.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h>
#include <AzToolsFramework/Editor/ActionManagerIdentifiers/EditorMenuIdentifiers.h>
#include <AzToolsFramework/Entity/EditorEntityActionComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/Entity/EditorEntityFixupComponent.h>
#include <AzToolsFramework/Entity/EditorEntityModelComponent.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Entity/EditorEntitySearchComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h>
#include <AzToolsFramework/FocusMode/FocusModeSystemComponent.h>
#include <AzToolsFramework/PaintBrush/GlobalPaintBrushSettingsSystemComponent.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
#include <AzToolsFramework/Render/EditorIntersectorComponent.h>
#include <AzToolsFramework/Script/LuaEditorSystemComponent.h>
#include <AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.h>
#include <AzToolsFramework/Slice/SliceDependencyBrowserComponent.h>
#include <AzToolsFramework/Slice/SliceMetadataEntityContextComponent.h>
#include <AzToolsFramework/Slice/SliceRequestComponent.h>
#include <AzToolsFramework/Slice/SliceUtilities.h>
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <AzToolsFramework/ToolsComponents/AzToolsFrameworkConfigurationSystemComponent.h>
#include <AzToolsFramework/ToolsComponents/ComponentAssetMimeDataContainer.h>
#include <AzToolsFramework/ToolsComponents/EditorAssetMimeDataContainer.h>
#include <AzToolsFramework/ToolsComponents/EditorInspectorComponent.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiSystemComponent.h>
#include <AzToolsFramework/UI/LegacyFramework/MainWindowSavedState.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.h>
#include <AzToolsFramework/UI/UICore/ProgressShield.hxx>
#include <AzToolsFramework/UI/UICore/QTreeViewStateSaver.hxx>
#include <AzToolsFramework/UI/UICore/QWidgetSavedState.h>
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
#include <AzToolsFramework/Viewport/ViewBookmarkSystemComponent.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.h>
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>

#include <Entity/EntityUtilityComponent.h>
#include <Metadata/MetadataManager.h>
#include <Prefab/ProceduralPrefabSystemComponent.h>
#include <AzToolsFramework/Metadata/UuidUtils.h>

#include <QtWidgets/QMessageBox>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
#include <QDir>
AZ_POP_DISABLE_WARNING
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMap>


// Not possible to use AZCore's operator new overrides until we address the overall problems
// with allocators, or more likely convert AzToolsFramework to a DLL and restrict overloading to
// within the DLL. Since this is currently linked as a lib, overriding new and delete would require
// us to hunt down all static allocs in Sandbox and Qt code.
// For now we'll stick with the CRT new/delete in tools.
//#include <AzCore/Memory/NewAndDelete.inl>

namespace AzToolsFramework
{
    namespace Internal
    {
        struct ToolsApplicationNotificationBusHandler final
            : public ToolsApplicationNotificationBus::Handler
            , public AZ::BehaviorEBusHandler
        {
            AZ_EBUS_BEHAVIOR_BINDER(ToolsApplicationNotificationBusHandler, "{7EB67956-FF86-461A-91E2-7B08279CFACF}", AZ::SystemAllocator,
                EntityRegistered, EntityDeregistered, AfterEntitySelectionChanged);

            void EntityRegistered(AZ::EntityId entityId) override
            {
                Call(FN_EntityRegistered, entityId);
            }

            void EntityDeregistered(AZ::EntityId entityId) override
            {
                Call(FN_EntityDeregistered, entityId);
            }

            void AfterEntitySelectionChanged(const EntityIdList& newlySelectedEntities, const EntityIdList& newlyDeselectedEntities) override
            {
                Call(FN_AfterEntitySelectionChanged, newlySelectedEntities, newlyDeselectedEntities);
            }
        };

        struct ViewPaneCallbackBusHandler final
            : public ViewPaneCallbackBus::Handler
            , public AZ::BehaviorEBusHandler
        {
            AZ_EBUS_BEHAVIOR_BINDER(ViewPaneCallbackBusHandler, "{3C4C85D4-0F0F-4ECA-A635-142F5DF68CF8}", AZ::SystemAllocator,
                CreateViewPaneWidget);

            AZ::u64 CreateViewPaneWidget() override
            {
                AZ::u64 widgetWindowId;
                CallResult(widgetWindowId, FN_CreateViewPaneWidget);

                return widgetWindowId;
            }
        };

        struct EditorEventsBusHandler final
            : public EditorEventsBus::Handler
            , public AZ::BehaviorEBusHandler
        {
            AZ_EBUS_BEHAVIOR_BINDER(EditorEventsBusHandler, "{352F80BB-469A-40B6-B322-FE57AB51E4DA}", AZ::SystemAllocator,
                NotifyRegisterViews, NotifyEditorInitialized);

            void NotifyRegisterViews() override
            {
                Call(FN_NotifyRegisterViews);
            }

            void NotifyEditorInitialized() override
            {
                Call(FN_NotifyEditorInitialized);
            }
        };

    } // Internal

    ToolsApplication::ToolsApplication(AZ::ComponentApplicationSettings componentAppSettings)
        : ToolsApplication(nullptr, nullptr, AZStd::move(componentAppSettings))
    {
    }
    ToolsApplication::ToolsApplication(int* argc, char*** argv)
        : ToolsApplication(argc, argv, {})
    {
    }
    ToolsApplication::ToolsApplication(int* argc, char*** argv, AZ::ComponentApplicationSettings componentAppSettings)
        : AzFramework::Application(argc, argv, AZStd::move(componentAppSettings))
        , m_selectionBounds(AZ::Aabb())
        , m_undoStack(nullptr)
        , m_currentBatchUndo(nullptr)
        , m_isDuringUndoRedo(false)
        , m_isInIsolationMode(false)
    {
        ToolsApplicationRequests::Bus::Handler::BusConnect();
        AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusConnect();
    }

    ToolsApplication::~ToolsApplication()
    {
        AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusDisconnect();
        ToolsApplicationRequests::Bus::Handler::BusDisconnect();
        Stop();
    }

    void ToolsApplication::CreateStaticModules(AZStd::vector<AZ::Module*>& outModules)
    {
        AzFramework::Application::CreateStaticModules(outModules);
        outModules.emplace_back(aznew AzFramework::AzFrameworkNativeUIModule());
        outModules.emplace_back(aznew AzToolsFrameworkModule);
    }

    AZ::ComponentTypeList ToolsApplication::GetRequiredSystemComponents() const
    {
        AZ::ComponentTypeList components = AzFramework::Application::GetRequiredSystemComponents();

        components.insert(components.end(), {
                azrtti_typeid<ActionManagerSystemComponent>(),
                azrtti_typeid<EditorEntityContextComponent>(),
                azrtti_typeid<Components::EditorEntityUiSystemComponent>(),
                azrtti_typeid<FocusModeSystemComponent>(),
                azrtti_typeid<ContainerEntitySystemComponent>(),
                azrtti_typeid<ReadOnlyEntitySystemComponent>(),
                azrtti_typeid<SliceMetadataEntityContextComponent>(),
                azrtti_typeid<Prefab::PrefabSystemComponent>(),
                azrtti_typeid<Prefab::ProceduralPrefabSystemComponent>(),
                azrtti_typeid<EditorEntityFixupComponent>(),
                azrtti_typeid<AzToolsFramework::ViewBookmarkSystemComponent>(),
                azrtti_typeid<Components::EditorComponentAPIComponent>(),
                azrtti_typeid<Components::EditorLevelComponentAPIComponent>(),
                azrtti_typeid<Components::EditorEntityActionComponent>(),
                azrtti_typeid<Components::PropertyManagerComponent>(),
                azrtti_typeid<AssetSystem::AssetSystemComponent>(),
                azrtti_typeid<PerforceComponent>(),
                azrtti_typeid<AzToolsFramework::AssetBundleComponent>(),
                azrtti_typeid<AzToolsFramework::ArchiveComponent>(),
                azrtti_typeid<AzToolsFramework::SliceDependencyBrowserComponent>(),
                azrtti_typeid<AzToolsFramework::AzToolsFrameworkConfigurationSystemComponent>(),
                azrtti_typeid<Components::EditorEntityModelComponent>(),
                azrtti_typeid<AzToolsFramework::EditorInteractionSystemComponent>(),
                azrtti_typeid<Components::EditorEntitySearchComponent>(),
                azrtti_typeid<Components::EditorIntersectorComponent>(),
                azrtti_typeid<AzToolsFramework::SliceRequestComponent>(),
                azrtti_typeid<AzToolsFramework::EntityUtilityComponent>(),
                azrtti_typeid<AzToolsFramework::Script::LuaSymbolsReporterSystemComponent>(),
                azrtti_typeid<AzToolsFramework::Script::LuaEditorSystemComponent>(),
                azrtti_typeid<AzToolsFramework::GlobalPaintBrushSettingsSystemComponent>(),
                azrtti_typeid<AzToolsFramework::MetadataManager>(),
                azrtti_typeid<AzToolsFramework::UuidUtilComponent>(),
                azrtti_typeid<AzFramework::NativeUISystemComponent>(),
            });

        return components;
    }

    void ToolsApplication::Start(const Descriptor& descriptor, const StartupParameters& startupParameters/* = StartupParameters()*/)
    {
        // GameApplications can run without an engine, but ToolsApplications need an engine
        // NOTE: we do not check 'FilePathKey_EngineRootFolder' because that might have been
        // set to the project's path which is not enough for ToolsApplications
        if (AZ::SettingsRegistryMergeUtils::FindEngineRoot(*m_settingsRegistry).empty())
        {
            ReportBadEngineRoot();
            return;
        }

        Application::Start(descriptor, startupParameters);
        if (!m_isStarted)
        {
            return;
        }

        m_editorEntityManager.Start();

        m_editorEntityAPI = AZ::Interface<EditorEntityAPI>::Get();
        AZ_Assert(m_editorEntityAPI, "ToolsApplication - Could not retrieve instance of EditorEntityAPI");
    }

    void ToolsApplication::StartCommon(AZ::Entity* systemEntity)
    {
        Application::StartCommon(systemEntity);

        m_undoStack = new UndoSystem::UndoStack(10, nullptr);
    }

    void ToolsApplication::Stop()
    {
        if (m_isStarted)
        {
            FlushUndo();

            auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
            if (undoCacheInterface)
            {
                undoCacheInterface->Clear();
            }

            delete m_undoStack;
            m_undoStack = nullptr;

            // Release any memory used by ToolsApplication before Application::Stop() destroys the allocators.
            m_selectedEntities.set_capacity(0);
            m_highlightedEntities.set_capacity(0);
            m_dirtyEntities = {};

            // This resets the editor context thereby asking the systems that own the entities to destroy them. By doing this, we are
            // duly giving the authority to delete the entities to the systems that owns them, rather than leaving it to the
            // ComponentApplication to do the cleanup.
            AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
                &AzToolsFramework::EditorEntityContextRequestBus::Events::ResetEditorContext);

            GetSerializeContext()->DestroyEditContext();

            Application::Stop();
        }
    }

    void ToolsApplication::CreateReflectionManager()
    {
        Application::CreateReflectionManager();

        GetSerializeContext()->CreateEditContext();
    }

    void ToolsApplication::Reflect(AZ::ReflectContext* context)
    {
        Application::Reflect(context);

        Components::EditorComponentBase::Reflect(context);
        EditorAssetMimeDataContainer::Reflect(context);
        ComponentAssetMimeDataContainer::Reflect(context);

        AssetEditor::AssetEditorWindowSettings::Reflect(context);
        AssetEditor::AssetEditorWidgetUserSettings::Reflect(context);

        QTreeViewWithStateSaving::Reflect(context);
        QWidgetSavedState::Reflect(context);
        SliceUtilities::Reflect(context);
        Prefab::PrefabIntegrationManager::Reflect(context);

        ComponentModeFramework::ComponentModeDelegate::Reflect(context);
        ComponentModeFramework::EditorBaseComponentMode::Reflect(context);

        BoxComponentMode::Reflect(context);
        CapsuleComponentMode::Reflect(context);
        SphereComponentMode::Reflect(context);
        CylinderComponentMode::Reflect(context);

        ViewportInteraction::ViewportInteractionReflect(context);
        ViewportEditorModeNotifications::Reflect(context);

        Camera::EditorCameraRequests::Reflect(context);
        AzToolsFramework::EditorTransformComponentSelectionRequests::Reflect(context);

        if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
        {
            // wrap BeginUndoBatch so we do not have to return a pointer to URSequencePoint which is not currently exposed to Python
            // note: This could be added as a 'handle' that could then be resolved as a pointer but this functionality is not needed yet
            auto BeginUndoBatchWrapper = [](ToolsApplicationRequests*, const char* name) {
                AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
                    &AzToolsFramework::ToolsApplicationRequestBus::Events::BeginUndoBatch, name);
            };

            behaviorContext->EBus<ToolsApplicationRequestBus>("ToolsApplicationRequestBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
                ->Attribute(AZ::Script::Attributes::Category, "Editor")
                ->Attribute(AZ::Script::Attributes::Module, "editor")
                ->Event("CreateNewEntity", &ToolsApplicationRequests::CreateNewEntity)
                ->Event("CreateNewEntityAtPosition", &ToolsApplicationRequests::CreateNewEntityAtPosition)
                ->Event("GetCurrentLevelEntityId", &ToolsApplicationRequests::GetCurrentLevelEntityId)
                ->Event("GetExistingEntity", &ToolsApplicationRequests::GetExistingEntity)
                ->Event("EntityExists", &ToolsApplicationRequests::EntityExists)
                ->Event("DeleteEntityById", &ToolsApplicationRequests::DeleteEntityById)
                ->Event("DeleteEntities", &ToolsApplicationRequests::DeleteEntities)
                ->Event("DeleteEntityAndAllDescendants", &ToolsApplicationRequests::DeleteEntityAndAllDescendants)
                ->Event("DeleteEntitiesAndAllDescendants", &ToolsApplicationRequests::DeleteEntitiesAndAllDescendants)
                ->Event("SetSelectedEntities", &ToolsApplicationRequests::SetSelectedEntities)
                ->Event("GetSelectedEntities", &ToolsApplicationRequests::GetSelectedEntities)
                ->Event("MarkEntitiesSelected", &ToolsApplicationRequests::MarkEntitiesSelected)
                ->Event("MarkEntitiesDeselected", &ToolsApplicationRequests::MarkEntitiesDeselected)
                ->Event("MarkEntitySelected", &ToolsApplicationRequests::MarkEntitySelected)
                ->Event("MarkEntityDeselected", &ToolsApplicationRequests::MarkEntityDeselected)
                ->Event("IsSelected", &ToolsApplicationRequests::IsSelected)
                ->Event("AreAnyEntitiesSelected", &ToolsApplicationRequests::AreAnyEntitiesSelected)
                ->Event("GetSelectedEntitiesCount", &ToolsApplicationRequests::GetSelectedEntitiesCount)
                ->Event("BeginUndoBatch", BeginUndoBatchWrapper)
                ->Event("EndUndoBatch", &ToolsApplicationRequests::EndUndoBatch)
                ->Event("AddDirtyEntity", &ToolsApplicationRequests::AddDirtyEntity)
                ;

            behaviorContext->EBus<ToolsApplicationNotificationBus>("ToolsApplicationNotificationBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
                ->Attribute(AZ::Script::Attributes::Category, "Editor")
                ->Attribute(AZ::Script::Attributes::Module, "editor")
                ->Handler<Internal::ToolsApplicationNotificationBusHandler>()
                ->Event("EntityRegistered", &ToolsApplicationEvents::EntityRegistered)
                ->Event("EntityDeregistered", &ToolsApplicationEvents::EntityDeregistered)
                ->Event("AfterEntitySelectionChanged", &ToolsApplicationEvents::AfterEntitySelectionChanged)
                ;

            behaviorContext->Class<ViewPaneOptions>()
                ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
                ->Attribute(AZ::Script::Attributes::Module, "editor")
                ->Property("isDeletable", BehaviorValueProperty(&ViewPaneOptions::isDeletable))
                ->Property("showInMenu", BehaviorValueProperty(&ViewPaneOptions::showInMenu))
                ->Property("canHaveMultipleInstances", BehaviorValueProperty(&ViewPaneOptions::canHaveMultipleInstances))
                ->Property("isPreview", BehaviorValueProperty(&ViewPaneOptions::isPreview))
                ->Property("showOnToolsToolbar", BehaviorValueProperty(&ViewPaneOptions::showOnToolsToolbar))
                ->Property("toolbarIcon", BehaviorValueProperty(&ViewPaneOptions::toolbarIcon))
                ;

            behaviorContext->EBus<EditorRequestBus>("EditorRequestBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
                ->Attribute(AZ::Script::Attributes::Category, "Editor")
                ->Attribute(AZ::Script::Attributes::Module, "editor")
                ->Event("RegisterCustomViewPane", &EditorRequests::RegisterCustomViewPane)
                ->Event("UnregisterViewPane", &EditorRequests::UnregisterViewPane)
                ->Event("GetComponentTypeEditorIcon", &EditorRequests::GetComponentTypeEditorIcon)
                ->Event("IsLevelDocumentOpen", &EditorRequests::IsLevelDocumentOpen)
                ->Event("GetLevelName", &EditorRequests::GetLevelName)
                ;

            behaviorContext->EBus<EditorEventsBus>("EditorEventBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
                ->Attribute(AZ::Script::Attributes::Category, "Editor")
                ->Attribute(AZ::Script::Attributes::Module, "editor")
                ->Handler<Internal::EditorEventsBusHandler>()
                ->Event("NotifyRegisterViews", &EditorEvents::NotifyRegisterViews)
                ->Event("NotifyEditorInitialized", &EditorEvents::NotifyEditorInitialized)
                ;

            behaviorContext->EBus<ViewPaneCallbackBus>("ViewPaneCallbackBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
                ->Attribute(AZ::Script::Attributes::Category, "Editor")
                ->Attribute(AZ::Script::Attributes::Module, "editor")
                ->Handler<Internal::ViewPaneCallbackBusHandler>()
                ->Event("CreateViewPaneWidget", &ViewPaneCallbacks::CreateViewPaneWidget)
                ;

        }
    }

    bool ToolsApplication::AddEntity(AZ::Entity* entity)
    {
        const bool result = AzFramework::Application::AddEntity(entity);

        if (result)
        {
            ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::Bus::Events::EntityRegistered, entity->GetId());
        }

        return result;
    }

    bool ToolsApplication::RemoveEntity(AZ::Entity* entity)
    {
        AZ_PROFILE_FUNCTION(AzToolsFramework);

        auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
        if (undoCacheInterface)
        {
            undoCacheInterface->PurgeCache(entity->GetId());
        }

        MarkEntityDeselected(entity->GetId());
        SetEntityHighlighted(entity->GetId(), false);

        ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::Bus::Events::EntityDeregistered, entity->GetId());

        {
            AZ_PROFILE_SCOPE(AzToolsFramework, "ToolsApplication::RemoveEntity:CallApplicationRemoveEntity");
            if (AzFramework::Application::RemoveEntity(entity))
            {
                return true;
            }
        }

        return false;
    }

    void ToolsApplication::PreExportEntity(AZ::Entity& source, AZ::Entity& target)
    {
        AZ::SerializeContext* serializeContext = nullptr;
        AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
        AZ_Assert(serializeContext, "No serialization context found");

        const AZ::Entity::ComponentArrayType& editorComponents = source.GetComponents();

        // Propagate components from source entity to target, in preparation for exporting target.
        for (AZ::Component* component : editorComponents)
        {
            Components::EditorComponentBase* asEditorComponent =
                azrtti_cast<Components::EditorComponentBase*>(component);

            if (asEditorComponent)
            {
                const size_t oldComponentCount = target.GetComponents().size();

                asEditorComponent->BuildGameEntity(&target);

                // Applying same Id persistence trick as we do in the slice compiler. Once we're off levels,
                // this code all goes away and everything runs through the slice compiler.
                if (target.GetComponents().size() > oldComponentCount)
                {
                    AZ::Component* newComponent = target.GetComponents().back();
                    AZ_Error("Export", asEditorComponent->GetId() != AZ::InvalidComponentId, "For entity \"%s\", component \"%s\" doesn't have a valid component id",
                             source.GetName().c_str(), asEditorComponent->RTTI_GetType().ToString<AZStd::string>().c_str());
                    newComponent->SetId(asEditorComponent->GetId());
                }
            }
            else
            {
                // The component is already runtime-ready. I.e. it is not an editor component.
                // Clone the component and add it to the export entity
                AZ::Component* clonedComponent = serializeContext->CloneObject(component);
                target.AddComponent(clonedComponent);
            }
        }
    }

    void ToolsApplication::PostExportEntity(AZ::Entity& /*source*/, AZ::Entity& /*target*/)
    {
    }

    const char* ToolsApplication::GetCurrentConfigurationName() const
    {
#if defined(_RELEASE)
        return "ReleaseEditor";
#elif defined(_DEBUG)
        return "DebugEditor";
#else
        return "ProfileEditor";
#endif
    }

    void ToolsApplication::SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations)
    {
        AzFramework::Application::SetSettingsRegistrySpecializations(specializations);
        specializations.Append("tools");
    }

    void ToolsApplication::MarkEntitySelected(AZ::EntityId entityId)
    {
        AZ_PROFILE_FUNCTION(AzToolsFramework);

        if (m_freezeSelectionUpdates)
        {
            return;
        }

        AZ_Assert(entityId.IsValid(), "Invalid entity Id being marked as selected.");

        EntityIdList::iterator foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
        if (foundIter == m_selectedEntities.end())
        {
            ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);

            m_selectedEntities.push_back(entityId);
            EntitySelectionEvents::Bus::Event(entityId, &EntitySelectionEvents::OnSelected);

            const AzToolsFramework::EntityIdList newlySelectedEntities = { entityId };
            ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntitySelectionChanged, newlySelectedEntities, AzToolsFramework::EntityIdList());
        }
    }

    void ToolsApplication::MarkEntitiesSelected(const EntityIdList& entitiesToSelect)
    {
        AZ_PROFILE_FUNCTION(AzToolsFramework);

        if (m_freezeSelectionUpdates)
        {
            return;
        }

        EntityIdList entitiesSelected;
        entitiesSelected.reserve(entitiesToSelect.size());

        ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);

        for (AZ::EntityId entityId : entitiesToSelect)
        {
            AZ_Assert(entityId.IsValid(), "Invalid entity Id being marked as selected.");
            EntityIdList::iterator foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
            if (foundIter == m_selectedEntities.end())
            {
                m_selectedEntities.push_back(entityId);
                EntitySelectionEvents::Bus::Event(entityId, &EntitySelectionEvents::OnSelected);
                entitiesSelected.push_back(entityId);
            }
        }

        ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntitySelectionChanged, entitiesSelected, EntityIdList());
    }

    void ToolsApplication::MarkEntityDeselected(AZ::EntityId entityId)
    {
        AZ_PROFILE_FUNCTION(AzToolsFramework);

        if (m_freezeSelectionUpdates)
        {
            return;
        }

        auto foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
        if (foundIter != m_selectedEntities.end())
        {
            AZ_PROFILE_SCOPE(AzToolsFramework, "ToolsApplication::MarkEntityDeselected:Deselect");
            ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);
            m_selectedEntities.erase(foundIter);

            EntitySelectionEvents::Bus::Event(entityId, &EntitySelectionEvents::OnDeselected);
            AzToolsFramework::EntityIdList newlyDeselectedEntities = { entityId };
            ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntitySelectionChanged, AzToolsFramework::EntityIdList(), newlyDeselectedEntities);
        }
    }

    void ToolsApplication::MarkEntitiesDeselected(const EntityIdList& entitiesToDeselect)
    {
        AZ_PROFILE_FUNCTION(AzToolsFramework);

        if (m_freezeSelectionUpdates)
        {
            return;
        }

        ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);

        EntityIdSet entitySetToDeselect(entitiesToDeselect.begin(), entitiesToDeselect.end());

        for (auto selectedEntityIt = m_selectedEntities.begin(); selectedEntityIt != m_selectedEntities.end(); )
        {
            auto foundIt = entitySetToDeselect.find(*selectedEntityIt);
            if (foundIt == entitySetToDeselect.end())
            {
                // not trying to deselect this entity, advance iterator
                ++selectedEntityIt;
            }
            else
            {
                EntitySelectionEvents::Bus::Event(*selectedEntityIt, &EntitySelectionEvents::OnDeselected);

                // swap with last element and pop back to avoid moving all vector elements, don't advance iterator
                // the order of m_selectedEntities does not matter
                AZStd::iter_swap(selectedEntityIt, m_selectedEntities.rbegin());
                m_selectedEntities.pop_back();
            }
        }

        ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntitySelectionChanged, EntityIdList(), entitiesToDeselect);
    }

    void ToolsApplication::SetEntityHighlighted(AZ::EntityId entityId, bool highlighted)
    {
        AZ_PROFILE_FUNCTION(AzToolsFramework);

        auto foundIter = AZStd::find(m_highlightedEntities.begin(), m_highlightedEntities.end(), entityId);
        if (foundIter != m_highlightedEntities.end())
        {
            if (!highlighted)
            {
                AZ_PROFILE_SCOPE(AzToolsFramework, "ToolsApplication::SetEntityHighlighted:RemoveHighlight");
                ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntityHighlightingChanged);
                m_highlightedEntities.erase(foundIter);
                ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntityHighlightingChanged);
            }
        }
        else if (highlighted)
        {
            AZ_PROFILE_SCOPE(AzToolsFramework, "ToolsApplication::SetEntityHighlighted:AddHighlight");
            ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntityHighlightingChanged);
            m_highlightedEntities.push_back(entityId);
            ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntityHighlightingChanged);
        }
    }

    void ToolsApplication::SetSelectedEntities(const EntityIdList& selectedEntities)
    {
        AZ_PROFILE_FUNCTION(AzToolsFramework);

        if (m_freezeSelectionUpdates)
        {
            return;
        }

        // We're setting the selection set as a batch from an external caller.
        // * Filter out any unselectable entities
        // * Calculate selection/deselection delta so we can notify specific entities only on change.
        // * Filter any duplicates.

        // Filter out any invalid or non-selectable entities
        EntityIdList selectedEntitiesFiltered;
        selectedEntitiesFiltered.reserve(selectedEntities.size());

        // if the new viewport interaction model is enabled we do not want to
        // filter out locked entities as this breaks with the logic of being
        // able to select locked entities in the entity outliner
        selectedEntitiesFiltered.insert(
            selectedEntitiesFiltered.begin(), selectedEntities.begin(), selectedEntities.end());

        EntityIdList newlySelectedIds;
        EntityIdList newlyDeselectedIds;

        // Populate list of newly selected entities
        for (AZ::EntityId nowSelectedId : selectedEntitiesFiltered)
        {
            auto alreadySelectedIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), nowSelectedId);

            if (alreadySelectedIter == m_selectedEntities.end())
            {
                newlySelectedIds.push_back(nowSelectedId);
            }
        }

        // Populate list of newly deselected entities
        for (AZ::EntityId currentlySelectedId : m_selectedEntities)
        {
            auto stillSelectedIter = AZStd::find(selectedEntitiesFiltered.begin(), selectedEntitiesFiltered.end(), currentlySelectedId);

            if (stillSelectedIter == selectedEntitiesFiltered.end())
            {
                newlyDeselectedIds.push_back(currentlySelectedId);
            }
        }

        // Apply selection changes in bulk
        if (!newlySelectedIds.empty() || !newlyDeselectedIds.empty())
        {
            ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);

            m_selectedEntities.clear();

            // Guarantee no dupes, since external caller is unknown.
            for (AZ::EntityId id : selectedEntitiesFiltered)
            {
                auto foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), id);
                if (foundIter == m_selectedEntities.end())
                {
                    m_selectedEntities.push_back(id);
                }
            }

            for (AZ::EntityId id : newlySelectedIds)
            {
                EntitySelectionEvents::Bus::Event(id, &EntitySelectionEvents::OnSelected);
            }

            for (AZ::EntityId id : newlyDeselectedIds)
            {
                EntitySelectionEvents::Bus::Event(id, &EntitySelectionEvents::OnDeselected);
            }

            ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntitySelectionChanged, newlySelectedIds, newlyDeselectedIds);
        }

        // ensure parent expansion happens if necessary, even if selection hasn't changed
        for (AZ::EntityId nowSelectedId : selectedEntities)
        {
            EditorEntityInfoNotificationBus::Broadcast(&EditorEntityInfoNotificationBus::Events::OnEntityInfoUpdatedSelection, nowSelectedId, true);
        }
    }

    EntityIdSet ToolsApplication::GatherEntitiesAndAllDescendents(const EntityIdList& inputEntities)
    {
        EntityIdSet output;
        EntityIdList tempList;
        for (const AZ::EntityId& id : inputEntities)
        {
            output.insert(id);

            tempList.clear();
            AZ::TransformBus::EventResult(tempList, id, &AZ::TransformBus::Events::GetAllDescendants);
            output.insert(tempList.begin(), tempList.end());
        }

        return output;
    }

    bool ToolsApplication::IsSelectable(const AZ::EntityId& /*entityId*/)
    {
        return true;
    }

    bool ToolsApplication::IsSelected(const AZ::EntityId& entityId)
    {
        return m_selectedEntities.end() != AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
    }

    bool ToolsApplication::IsSliceRootEntity(const AZ::EntityId& entityId)
    {
        return SliceUtilities::IsSliceOrSubsliceRootEntity(entityId);
    }

    AZ::EntityId ToolsApplication::CreateNewEntity(AZ::EntityId parentId)
    {
        AZ::EntityId createdEntityId;
        EditorRequestBus::BroadcastResult(createdEntityId, &EditorRequests::CreateNewEntity, parentId);

        return createdEntityId;
    }

    AZ::EntityId ToolsApplication::CreateNewEntityAtPosition(const AZ::Vector3& pos, AZ::EntityId parentId)
    {
        AZ::EntityId createdEntityId;
        EditorRequestBus::BroadcastResult(createdEntityId, &EditorRequests::CreateNewEntityAtPosition, pos, parentId);

        return createdEntityId;
    }

    AZ::EntityId ToolsApplication::GetExistingEntity(AZ::u64 id)
    {
        return AZ::EntityId{id};
    }

    bool ToolsApplication::EntityExists(AZ::EntityId id)
    {
        return FindEntity(id) != nullptr;
    }

    void ToolsApplication::DeleteSelected()
    {
        m_editorEntityAPI->DeleteSelected();
    }

    void ToolsApplication::DeleteEntityAndAllDescendants(AZ::EntityId entityId)
    {
        m_editorEntityAPI->DeleteEntityAndAllDescendants(entityId);
    }

    void ToolsApplication::DeleteEntitiesAndAllDescendants(const EntityIdList& entities)
    {
        m_editorEntityAPI->DeleteEntitiesAndAllDescendants(entities);
    }

    bool ToolsApplication::FindCommonRoot(const AzToolsFramework::EntityIdSet& entitiesToBeChecked, AZ::EntityId& commonRootEntityId
        , AzToolsFramework::EntityIdList* topLevelEntities)
    {
        // Return value
        bool entitiesHaveCommonRoot = true;

        bool rootFound = false;
        commonRootEntityId.SetInvalid();

        // Finding the common root and top level entities
        for (const AZ::EntityId& id : entitiesToBeChecked)
        {
            AZ_Warning("Slices", AZ::TransformBus::FindFirstHandler(id) != nullptr,
                       "Entity with id %llu is not active, or does not have a transform component. This method is only valid for active entities. with a Transform component",
                       id);

            AZ::EntityId parentId;
            AZ::TransformBus::EventResult(parentId, id, &AZ::TransformBus::Events::GetParentId);

            // If this entity is not a child of entities to be checked
            if (entitiesToBeChecked.find(parentId) == entitiesToBeChecked.end())
            {
                if (topLevelEntities)
                {
                    // Add it to the top level entities
                    topLevelEntities->push_back(id);
                }

                if (rootFound)
                {
                    // If the entities checked until now have a common root and the parent id of this entity is not the same as the common root
                    if (entitiesHaveCommonRoot && (parentId != commonRootEntityId))
                    {
                        // Entities do not have a common root
                        commonRootEntityId.SetInvalid();
                        entitiesHaveCommonRoot = false;
                    }
                }
                else
                {
                    commonRootEntityId = parentId;
                    rootFound = true;
                }
            }
        }

        return entitiesHaveCommonRoot;
    }

    bool ToolsApplication::FindCommonRootInactive(const AzToolsFramework::EntityList& entitiesToBeChecked, AZ::EntityId& commonRootEntityId, AzToolsFramework::EntityList* topLevelEntities)
    {
        // Return value
        bool entitiesHaveCommonRoot = true;

        bool rootFound = false;
        commonRootEntityId.SetInvalid();

        AzToolsFramework::EntityIdSet entityIds;
        for (const auto& entity : entitiesToBeChecked)
        {
            entityIds.insert(entity->GetId());
        }

        // Finding the common root and top level entities
        for (const AZ::Entity* entity : entitiesToBeChecked)
        {
            Components::TransformComponent* transformComponent =
                entity->FindComponent<Components::TransformComponent>();

            if (transformComponent)
            {
                const AZ::EntityId& parentId = transformComponent->GetParentId();
                // If this entity is not a child of entities to be checked
                if (entityIds.find(parentId) == entityIds.end())
                {
                    if (topLevelEntities)
                    {
                        topLevelEntities->push_back(const_cast<AZ::Entity*>(entity));
                    }

                    if (rootFound)
                    {
                        if (entitiesHaveCommonRoot && (parentId != commonRootEntityId))
                        {
                            commonRootEntityId.SetInvalid();
                            entitiesHaveCommonRoot = false;
                        }
                    }
                    else
                    {
                        commonRootEntityId = parentId;
                        rootFound = true;
                    }
                }
            }
        }

        return entitiesHaveCommonRoot;
    }

    void ToolsApplication::FindTopLevelEntityIdsInactive(const EntityIdList& entityIdsToCheck, EntityIdList& topLevelEntityIds)
    {
        for (AZ::EntityId entityId : entityIdsToCheck)
        {
            AZ::Entity* entity = nullptr;
            AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
            if (!entity)
            {
                continue;
            }
            Components::TransformComponent* transformComponent = entity->FindComponent<Components::TransformComponent>();
            if (!transformComponent)
            {
                continue;
            }

            bool isTopLevel = true;
            AZ::EntityId parentId = transformComponent->GetParentId();
            while (parentId.IsValid())
            {
                if (AZStd::find(entityIdsToCheck.begin(), entityIdsToCheck.end(), parentId) != entityIdsToCheck.end())
                {
                    isTopLevel = false;
                    break;
                }
                else
                {
                    AZ::Entity* parentEntity = nullptr;
                    AZ::ComponentApplicationBus::BroadcastResult(parentEntity, &AZ::ComponentApplicationBus::Events::FindEntity, parentId);
                    parentId.SetInvalid();
                    if (parentEntity)
                    {
                        transformComponent = parentEntity->FindComponent<Components::TransformComponent>();
                        if (transformComponent)
                        {
                            parentId = transformComponent->GetParentId();
                        }
                    }
                }
            }

            if (isTopLevel)
            {
                topLevelEntityIds.push_back(entityId);
            }
        }
    }

    AZ::SliceComponent::SliceInstanceAddress ToolsApplication::FindCommonSliceInstanceAddress(const EntityIdList& entityIds)
    {
        AZ::SliceComponent::SliceInstanceAddress result;

        if (entityIds.empty())
        {
            return result;
        }

        AzFramework::SliceEntityRequestBus::EventResult(result, entityIds[0],
            &AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);

        for (int index = 1; index < entityIds.size(); index++)
        {
            AZ::SliceComponent::SliceInstanceAddress sliceAddressTemp;
            AzFramework::SliceEntityRequestBus::EventResult(sliceAddressTemp, entityIds[index],
                &AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);

            if (sliceAddressTemp != result)
            {
                // Entities come from different slice instances.
                return AZ::SliceComponent::SliceInstanceAddress();
            }
        }

        return result;
    }

    AZ::EntityId ToolsApplication::GetRootEntityIdOfSliceInstance(AZ::SliceComponent::SliceInstanceAddress sliceAddress)
    {
        AZ::EntityId result;
        if (!sliceAddress.IsValid())
        {
            return result;
        }

        const AZ::Data::Asset<AZ::SliceAsset>& sliceAsset = sliceAddress.GetReference()->GetSliceAsset();
        AZ::SliceComponent::EntityList sliceAssetEntities;
        sliceAsset.Get()->GetComponent()->GetEntities(sliceAssetEntities);

        AZ::EntityId commonRootEntityId;
        AzToolsFramework::EntityList topLevelEntities;

        FindCommonRootInactive(sliceAssetEntities, commonRootEntityId, &topLevelEntities);
        if (!topLevelEntities.empty())
        {
            const AZ::SliceComponent::EntityIdToEntityIdMap& baseToInstanceEntityIdMap = sliceAddress.GetInstance()->GetEntityIdMap();
            auto foundItr = baseToInstanceEntityIdMap.find(topLevelEntities[0]->GetId());
            if (foundItr != baseToInstanceEntityIdMap.end())
            {
                result = foundItr->second;
            }
        }

        return result;
    }

    AZ::EntityId ToolsApplication::GetCurrentLevelEntityId()
    {
        if (auto prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get())
        {
            return prefabPublicInterface->GetLevelInstanceContainerEntityId();
        }

        return AZ::EntityId();
    }

    bool ToolsApplication::CheckSourceControlConnectionAndRequestEditForFileBlocking(const char* assetPath, const char* progressMessage, const ToolsApplicationRequests::RequestEditProgressCallback& progressCallback)
    {
        AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
        SourceControlState state = SourceControlState::Disabled;
        SourceControlConnectionRequestBus::BroadcastResult(state, &SourceControlConnectionRequestBus::Events::GetSourceControlState);
        if (state != SourceControlState::Active && (!fileIO || fileIO->IsReadOnly(assetPath)))
        {
            return false;
        }

        return RequestEditForFileBlocking(assetPath, progressMessage, progressCallback);
    }

    bool ToolsApplication::RequestEditForFileBlocking(const char* assetPath, const char* progressMessage, const ToolsApplicationRequests::RequestEditProgressCallback& progressCallback)
    {
        AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
        if (fileIO && !fileIO->IsReadOnly(assetPath))
        {
            SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, assetPath, true, [](bool, const SourceControlFileInfo&) {});
            return true;
        }

        bool editSuccess = false;
        bool editComplete = false;
        SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, assetPath, true,
            [&editSuccess, &editComplete](bool success, const AzToolsFramework::SourceControlFileInfo& /*info*/)
            {
                editSuccess = success;
                editComplete = true;
            }
        );

        QWidget* mainWindow = nullptr;
        EditorRequests::Bus::BroadcastResult(mainWindow, &EditorRequests::Bus::Events::GetMainWindow);
        AZ_Warning("RequestEdit", mainWindow, "Failed to retrieve main application window.");
        ProgressShield::LegacyShowAndWait(mainWindow, mainWindow ? mainWindow->tr(progressMessage) : QString(progressMessage),
            [&editComplete, &progressCallback](int& current, int& max)
            {
                if (progressCallback)
                {
                    progressCallback(current, max);
                }
                else
                {
                    current = 0;
                    max = 0;
                }
                return editComplete;
            }
        );

        return (fileIO && !fileIO->IsReadOnly(assetPath));
    }

    void ToolsApplication::RequestEditForFile(const char* assetPath, RequestEditResultCallback resultCallback)
    {
        AZ_Error("RequestEdit", resultCallback != nullptr, "User result callback is required.");

        AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
        if (fileIO && !fileIO->IsReadOnly(assetPath))
        {
            if (resultCallback)
            {
                resultCallback(true);
            }
            return;
        }

        SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, assetPath, true,
            [resultCallback](bool success, const AzToolsFramework::SourceControlFileInfo& info)
            {
                if (resultCallback)
                {
                    success = !info.IsReadOnly();
                    resultCallback(success);
                }
            }
        );
    }

    void ToolsApplication::CheckSourceControlConnectionAndRequestEditForFile(const char* assetPath, RequestEditResultCallback resultCallback)
    {
        AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
        SourceControlState state = SourceControlState::Disabled;
        SourceControlConnectionRequestBus::BroadcastResult(state, &SourceControlConnectionRequestBus::Events::GetSourceControlState);
        if (state != SourceControlState::Active && (!fileIO || fileIO->IsReadOnly(assetPath)))
        {
            resultCallback(false);
            return;
        }

        RequestEditForFile(assetPath, resultCallback);
    }

    void ToolsApplication::DeleteEntityById(AZ::EntityId entityId)
    {
        m_editorEntityAPI->DeleteEntityById(entityId);
    }

    void ToolsApplication::DeleteEntities(const EntityIdList& entities)
    {
        m_editorEntityAPI->DeleteEntities(entities);
    }

    void ToolsApplication::AddDirtyEntity(AZ::EntityId entityId)
    {
        // If we're already in undo redo, we don't want the user to have to check for this each time.
        if (m_isDuringUndoRedo)
        {
            return;
        }

        // Only accept entities as dirty between a begin/end undo batch. This filters out entities
        // that have been marked dirty by a non-user operation such as entity activation. This can
        // occur if SetDirty is called from a common function used in both user and non-user actions
        if (!m_currentBatchUndo)
        {
            return;
        }

        // Only accept entities as dirty if these were not added to the ignored list.
        // This filters out entities that have been marked as ignored by a non-user operation
        // such as specific controller destruction when such controller was added in a specific pipeline.
        if (m_ignoredEntities.contains(entityId))
        {
            AZ_Trace("ToolsApplication", "AddDirtyEntity(%s) exits as the entity was added to the ignored list.", entityId.ToString().c_str())
            return;
        }

        m_dirtyEntities.insert(entityId);
    }

    int ToolsApplication::RemoveDirtyEntity(AZ::EntityId entityId)
    {
        return static_cast<int>(m_dirtyEntities.erase(entityId));
    }

    void ToolsApplication::ClearDirtyEntities()
    {
        m_dirtyEntities.clear();
    }

    void ToolsApplication::AddIgnoredEntity(AZ::EntityId entityId)
    {
        m_ignoredEntities.insert(entityId);
    }

    int ToolsApplication::RemoveIgnoredEntity(AZ::EntityId entityId)
    {
        return static_cast<int>(m_ignoredEntities.erase(entityId));
    }

    void ToolsApplication::ClearIgnoredEntities()
    {
        m_ignoredEntities.clear();
    }

    void ToolsApplication::UndoPressed()
    {
        if (m_undoStack)
        {
            if (m_undoStack->CanUndo())
            {
                m_isDuringUndoRedo = true;
                ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::Bus::Events::BeforeUndoRedo);
                m_undoStack->Undo();
                ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::Bus::Events::AfterUndoRedo);
                m_isDuringUndoRedo = false;

#if defined(ENABLE_UNDOCACHE_CONSISTENCY_CHECKS)
                ConsistencyCheckUndoCache();
#endif
            }
        }
    }

    void ToolsApplication::RedoPressed()
    {
        if (m_undoStack)
        {
            if (m_undoStack->CanRedo())
            {
                m_isDuringUndoRedo = true;
                ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::Bus::Events::BeforeUndoRedo);
                m_undoStack->Redo();
                ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::Bus::Events::AfterUndoRedo);
                m_isDuringUndoRedo = false;

#if defined(ENABLE_UNDOCACHE_CONSISTENCY_CHECKS)
                ConsistencyCheckUndoCache();
#endif
            }
        }
    }

    void ToolsApplication::FlushUndo()
    {
        if (m_undoStack)
        {
            m_undoStack->Reset();
        }

        if (m_currentBatchUndo)
        {
            delete m_currentBatchUndo;
            m_currentBatchUndo = nullptr;
        }

        m_dirtyEntities.clear();
        m_isDuringUndoRedo = false;
    }

    void ToolsApplication::FlushRedo()
    {
        if (m_undoStack)
        {
            m_undoStack->Slice();
        }
    }

    UndoSystem::URSequencePoint* ToolsApplication::BeginUndoBatch(const char* label)
    {
        AZ_Error("Tools Application", !m_isDuringUndoRedo, "Can not create a new Undo/Redo bach while an Undo or Redo operation is running.");

        if (!m_currentBatchUndo)
        {
            m_currentBatchUndo = aznew UndoSystem::BatchCommand(label, 0);

            // notify Cry undo has started (SandboxIntegrationManager)
            // Only do this at the root level. OnEndUndo will be called at the root
            // level when EndUndoBatch is called.
            ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::Bus::Events::OnBeginUndo, label);
        }
        else
        {
            UndoSystem::URSequencePoint* current = m_currentBatchUndo;

            m_currentBatchUndo = aznew UndoSystem::BatchCommand(label, 0);
            m_currentBatchUndo->SetParent(current);
        }

        return m_currentBatchUndo;
    }

    UndoSystem::URSequencePoint* ToolsApplication::ResumeUndoBatch(UndoSystem::URSequencePoint* expected, const char* label)
    {
        if (m_currentBatchUndo)
        {
            if (m_undoStack->GetTop() == m_currentBatchUndo)
            {
                m_undoStack->PopTop();
            }

            return m_currentBatchUndo;
        }

        if (m_undoStack)
        {
            const auto ptr = m_undoStack->GetTop();
            if (ptr && ptr == expected)
            {
                m_currentBatchUndo = ptr;
                m_undoStack->PopTop();

                return m_currentBatchUndo;
            }
        }

        return BeginUndoBatch(label);
    }

    /**
     * Iterate over all sequence points for this undo batch, checking in each
     * case if it changed or not - if no change was recorded, discard this undo.
     */
    static bool DidSequencePointChange(const UndoSystem::URSequencePoint* root)
    {
        bool changed = false;
        const UndoSystem::URSequencePoint::ChildVec& children = root->GetChildren();
        if (!children.empty())
        {
            for (const UndoSystem::URSequencePoint* child : children)
            {
                changed = DidSequencePointChange(child);
                if (changed)
                {
                    break;
                }
            }
        }

        return root->Changed() || changed;
    }

    void ToolsApplication::EndUndoBatch()
    {
        AZ_Assert(m_currentBatchUndo, "Cannot end batch - no batch current");

        if (m_currentBatchUndo->GetParent())
        {
            // pop one up
            m_currentBatchUndo = m_currentBatchUndo->GetParent();
        }
        else
        {
            // we're at the root
            // only undo at bottom of scope (first invoked ScopedUndoBatch in
            // chain/hierarchy must go out of scope)
            CreateUndosForDirtyEntities();
            m_dirtyEntities.clear();

            // check if any change was recorded during this undo
            bool changed = DidSequencePointChange(m_currentBatchUndo);

            // notify Cry undo has ended (SandboxIntegrationManager)
            ToolsApplicationEvents::Bus::Broadcast(
                &ToolsApplicationEvents::Bus::Events::OnEndUndo, m_currentBatchUndo->GetName().c_str(), changed);

            // record each undo batch
            if (m_undoStack && changed)
            {
                m_undoStack->Post(m_currentBatchUndo);
            }
            else
            {
                delete m_currentBatchUndo;
            }

#if defined(ENABLE_UNDOCACHE_CONSISTENCY_CHECKS)
            ConsistencyCheckUndoCache();
#endif
            m_currentBatchUndo = nullptr;
        }
    }

    void ToolsApplication::OnPrefabInstancePropagationBegin()
    {
        m_freezeSelectionUpdates = true;
    }

    void ToolsApplication::OnPrefabInstancePropagationEnd()
    {
        m_freezeSelectionUpdates = false;
    }

    void ToolsApplication::CreateUndosForDirtyEntities()
    {
        AZ_PROFILE_FUNCTION(AzToolsFramework);

        AZ_Assert(!m_isDuringUndoRedo, "Cannot add dirty entities during undo/redo.");
        if (m_dirtyEntities.empty())
        {
            return;
        }

        auto prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
        if (prefabPublicInterface)
        {
            for (AZ::EntityId entityId : m_dirtyEntities)
            {
                auto outcome = prefabPublicInterface->GenerateUndoNodesForEntityChangeAndUpdateCache(entityId, m_currentBatchUndo);

                if (!outcome.IsSuccess())
                {
                    QMessageBox::warning(
                        AzToolsFramework::GetActiveWindow(), QString("Error"), QString(outcome.GetError().c_str()), QMessageBox::Ok, QMessageBox::Ok);
                }
            }
        }
    }

    void ToolsApplication::ConsistencyCheckUndoCache()
    {
        auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
        if (undoCacheInterface)
        {
            for (auto&& entityEntry : m_entities)
            {
                undoCacheInterface->Validate((entityEntry.second)->GetId());
            }
        }
    }

    bool ToolsApplication::IsEntityEditable(AZ::EntityId entityId)
    {
        (void)entityId;

        return true;
    }

    bool ToolsApplication::AreEntitiesEditable(const EntityIdList& entityIds)
    {
        for (AZ::EntityId entityId : entityIds)
        {
            if (!IsEntityEditable(entityId))
            {
                return false;
            }
        }

        return true;
    }

    void ToolsApplication::CheckoutPressed()
    {
    }

    SourceControlFileInfo ToolsApplication::GetSceneSourceControlInfo()
    {
        return SourceControlFileInfo();
    }

    void ToolsApplication::EnterEditorIsolationMode()
    {
        if (!m_isInIsolationMode && m_selectedEntities.size() > 0)
        {
            m_isInIsolationMode = true;

            m_isolatedEntityIdSet.insert(m_selectedEntities.begin(), m_selectedEntities.end());

            // Add also entities in between selected entities along the transform hierarchy chain.
            // For example A has child B and B has child C, and if A and C are isolated, B is too.
            AZStd::vector<AZ::EntityId> inbetweenEntityIds;

            for (AZ::EntityId entityId : m_selectedEntities)
            {
                bool addInbetweenEntityIds = false;
                inbetweenEntityIds.clear();

                AZ::EntityId parentEntityId;
                AZ::TransformBus::EventResult(parentEntityId, entityId, &AZ::TransformBus::Events::GetParentId);

                while (parentEntityId.IsValid())
                {
                    const EntityIdSet::iterator found = m_isolatedEntityIdSet.find(parentEntityId);
                    if (found != m_isolatedEntityIdSet.end())
                    {
                        addInbetweenEntityIds = true;
                        break;
                    }
                    inbetweenEntityIds.push_back(parentEntityId);

                    const AZ::EntityId currentParentId = parentEntityId;
                    parentEntityId.SetInvalid();
                    AZ::TransformBus::EventResult(parentEntityId, currentParentId, &AZ::TransformBus::Events::GetParentId);
                }

                if (addInbetweenEntityIds)
                {
                    m_isolatedEntityIdSet.insert(inbetweenEntityIds.begin(), inbetweenEntityIds.end());
                }
            }


            for (AZ::EntityId entityId : m_isolatedEntityIdSet)
            {
                ComponentEntityEditorRequestBus::Event(entityId, &ComponentEntityEditorRequestBus::Events::SetSandBoxObjectIsolated, true);
            }

            ToolsApplicationNotificationBus::Broadcast(&ToolsApplicationNotificationBus::Events::OnEnterEditorIsolationMode);
        }
    }

    void ToolsApplication::ExitEditorIsolationMode()
    {
        if (m_isInIsolationMode)
        {
            m_isInIsolationMode = false;

            for (AZ::EntityId entityId : m_isolatedEntityIdSet)
            {
                ComponentEntityEditorRequestBus::Event(entityId, &ComponentEntityEditorRequestBus::Events::SetSandBoxObjectIsolated, false);
            }

            m_isolatedEntityIdSet.clear();

            ToolsApplicationNotificationBus::Broadcast(&ToolsApplicationNotificationBus::Events::OnExitEditorIsolationMode);
        }
    }

    bool ToolsApplication::IsEditorInIsolationMode()
    {
        return m_isInIsolationMode;
    }

    void ToolsApplication::CreateAndAddEntityFromComponentTags(const AZStd::vector<AZ::Crc32>& requiredTags, const char* entityName)
    {
        if (!entityName || !entityName[0])
        {
            entityName = "ToolsApplication Entity";
        }

        AZ::SerializeContext* context = GetSerializeContext();
        AZ_Assert(context, "Unable to retrieve serialize context. Ensure the application is initialized before attempting to add components by tag.");

        AZ::Entity* entity = aznew AZ::Entity(entityName);
        AZStd::unordered_set<AZ::Uuid> componentsToAddToEntity;
        AZ::Edit::GetComponentUuidsWithSystemComponentTag(context, requiredTags, componentsToAddToEntity);

        for (const AZ::Uuid& typeId : componentsToAddToEntity)
        {
            entity->CreateComponent(typeId);
        }
        entity->Init();
        entity->Activate();
    }

    void ToolsApplication::RunRedoSeparately(UndoSystem::URSequencePoint* redoCommand)
    {
        if (redoCommand)
        {
            m_isDuringUndoRedo = true;
            redoCommand->RunRedo();
            m_isDuringUndoRedo = false;
        }
    }

    AzToolsFramework::ToolsApplicationRequests::ResolveToolPathOutcome ToolsApplication::ResolveConfigToolsPath(const char* toolApplicationName) const
    {
        if (!GetExecutableFolder() || !GetEngineRoot())
        {
            return AZ::Failure(AZStd::string("Cannot use ToolsApplicationRequets::ResolveConfigToolsPath until the component application has finished starting up."));
        }

        AZStd::vector<AZStd::string> toolTargetSearchPaths;

        // Add the current executable's folder to search paths
        {
            AZStd::string localAppPath;
            if (AzFramework::StringFunc::Path::ConstructFull(GetExecutableFolder(), toolApplicationName, localAppPath, true))
            {
                toolTargetSearchPaths.push_back(localAppPath);
            }
        }

        for (const AZStd::string& searchAbsPath : toolTargetSearchPaths)
        {
            if (AZ::IO::SystemFile::Exists(searchAbsPath.c_str()))
            {
                return AZ::Success(AZStd::string(searchAbsPath));
            }
        }

        return AZ::Failure(AZStd::string::format("Unable to resolve tool application path for '%s'", toolApplicationName));
    }

    void ToolsApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const
    {
        appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Tool;
    };
} // namespace AzToolsFramework
