/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "stdafx.h"

#include "EditorCommon.h"
#include "CanvasHelpers.h"
#include <AzQtComponents/Components/Style.h>
#include <AzToolsFramework/Slice/SliceUtilities.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponent.h>
#include <LyShine/Bus/UiSystemBus.h>

//-------------------------------------------------------------------------------

//we require an overlay widget to act as a canvas to draw on top of everything in the inspector
//attaching to inspector rather than component editors so we can draw outside of bounds
class PropertyContainerOverlay : public QWidget
{
public:
    PropertyContainerOverlay(PropertiesContainer* editor, QWidget* parent)
        : QWidget(parent)
        , m_editor(editor)
        , m_dropIndicatorOffset(8)
    {
        setPalette(Qt::transparent);
        setWindowFlags(Qt::FramelessWindowHint);
        setAttribute(Qt::WA_NoSystemBackground);
        setAttribute(Qt::WA_TranslucentBackground);
        setAttribute(Qt::WA_TransparentForMouseEvents);
    }

protected:
    void paintEvent(QPaintEvent* event) override
    {
        const int TopMargin = 1;
        const int RightMargin = 2;
        const int BottomMargin = 5;
        const int LeftMargin = 2;

        QWidget::paintEvent(event);

        QPainter painter(this);
        painter.setCompositionMode(QPainter::CompositionMode_SourceOver);

        QRect currRect;
        bool drag = false;
        bool drop = false;

        for (auto componentEditor : m_editor->m_componentEditors)
        {
            if (!componentEditor->isVisible())
            {
                continue;
            }

            QRect globalRect = m_editor->GetWidgetGlobalRect(componentEditor);

            currRect = QRect(
                QPoint(mapFromGlobal(globalRect.topLeft()) + QPoint(LeftMargin, TopMargin)),
                QPoint(mapFromGlobal(globalRect.bottomRight()) - QPoint(RightMargin, BottomMargin))
            );

            currRect.setWidth(currRect.width() - 1);
            currRect.setHeight(currRect.height() - 1);

            if (componentEditor->IsDragged())
            {
                QStyleOption opt;
                opt.init(this);
                opt.rect = currRect;
                static_cast<AzQtComponents::Style*>(style())->drawDragIndicator(&opt, &painter, this);
                drag = true;
            }

            if (componentEditor->IsDropTarget())
            {
                QRect dropRect = currRect;
                dropRect.setTop(currRect.top() - m_dropIndicatorOffset);
                dropRect.setHeight(0);

                QStyleOption opt;
                opt.init(this);
                opt.rect = dropRect;
                style()->drawPrimitive(QStyle::PE_IndicatorItemViewItemDrop, &opt, &painter, this);

                drop = true;
            }
        }

        if (drag && !drop)
        {
            QRect dropRect = currRect;
            dropRect.setTop(currRect.top() - m_dropIndicatorOffset);
            dropRect.setHeight(0);

            QStyleOption opt;
            opt.init(this);
            opt.rect = dropRect;
            style()->drawPrimitive(QStyle::PE_IndicatorItemViewItemDrop, &opt, &painter, this);
        }
    }

private:
    PropertiesContainer* m_editor;
    int m_dropIndicatorOffset;
};

//-------------------------------------------------------------------------------

PropertiesContainer::PropertiesContainer(PropertiesWidget* propertiesWidget,
    EditorWindow* editorWindow)
    : QScrollArea(propertiesWidget)
    , m_propertiesWidget(propertiesWidget)
    , m_editorWindow(editorWindow)
    , m_selectedEntityDisplayNameWidget(nullptr)
    , m_selectionHasChanged(false)
    , m_isCanvasSelected(false)
    , m_selectionEventAccepted(false)
    , m_componentEditorLastSelectedIndex(-1)
{
    setFocusPolicy(Qt::ClickFocus);
    setFrameShape(QFrame::NoFrame);
    setFrameShadow(QFrame::Plain);
    setLineWidth(0);
    setWidgetResizable(true);

    m_componentListContents = new QWidget();
    m_componentListContents->setGeometry(QRect(0, 0, 382, 537));
    QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
    sizePolicy.setHeightForWidth(m_componentListContents->sizePolicy().hasHeightForWidth());
    m_componentListContents->setSizePolicy(sizePolicy);
    m_rowLayout = new QVBoxLayout(m_componentListContents);
    m_rowLayout->setSpacing(10);
    m_rowLayout->setContentsMargins(0, 0, 0, 0);
    m_rowLayout->setAlignment(Qt::AlignTop);

    setWidget(m_componentListContents);

    m_overlay = new PropertyContainerOverlay(this, m_componentListContents);
    UpdateOverlay();

    CreateActions();

    // Get the serialize context.
    EBUS_EVENT_RESULT(m_serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
    AZ_Assert(m_serializeContext, "We should have a valid context!");

    QObject::connect(m_editorWindow->GetHierarchy(),
        &HierarchyWidget::editorOnlyStateChangedOnSelectedElements,
        [this]()
    {
        UpdateEditorOnlyCheckbox();
    });
}

void PropertiesContainer::resizeEvent(QResizeEvent* event)
{
    QScrollArea::resizeEvent(event);
    UpdateOverlay();
}

void PropertiesContainer::contextMenuEvent(QContextMenuEvent* event)
{
    OnDisplayUiComponentEditorMenu(event->globalPos());
    event->accept();
}

//overridden to intercept application level mouse events for component editor selection
bool PropertiesContainer::eventFilter(QObject* object, QEvent* event)
{
    HandleSelectionEvents(object, event);
    return false;
}

bool PropertiesContainer::HandleSelectionEvents(QObject* object, QEvent* event)
{
    (void)object;
    if (m_selectedEntities.empty())
    {
        return false;
    }

    if (event->type() != QEvent::MouseButtonPress &&
        event->type() != QEvent::MouseButtonDblClick &&
        event->type() != QEvent::MouseButtonRelease)
    {
        return false;
    }

    QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);

    //selection now occurs on mouse released
    //reset selection flag when mouse is clicked to allow additional selection changes
    if (event->type() == QEvent::MouseButtonPress)
    {
        m_selectionEventAccepted = false;
        return false;
    }

    //reject input if selection already occurred for this click
    if (m_selectionEventAccepted)
    {
        return false;
    }

    //reject input if a popup or modal window is active
    if (QApplication::activeModalWidget() != nullptr ||
        QApplication::activePopupWidget() != nullptr)
    {
        return false;
    }

    const QRect globalRect(mouseEvent->globalPos(), mouseEvent->globalPos());

    //reject input outside of the inspector's component list
    if (!DoesOwnFocus() ||
        !DoesIntersectWidget(globalRect, this))
    {
        return false;
    }

    //reject input from other buttons
    if ((mouseEvent->button() != Qt::LeftButton) &&
        (mouseEvent->button() != Qt::RightButton))
    {
        return false;
    }

    //right click is allowed if the component editor under the mouse is not selected
    if (mouseEvent->button() == Qt::RightButton)
    {
        if (DoesIntersectSelectedComponentEditor(globalRect))
        {
            return false;
        }

        ClearComponentEditorSelection();
        SelectIntersectingComponentEditors(globalRect, true);
    }
    else if (mouseEvent->button() == Qt::LeftButton)
    {
        //if shift or control is pressed this is a multi=-select operation, otherwise reset the selection
        if (mouseEvent->modifiers() & Qt::ControlModifier)
        {
            ToggleIntersectingComponentEditors(globalRect);
        }
        else if (mouseEvent->modifiers() & Qt::ShiftModifier)
        {
            ComponentEditorVector intersections = GetIntersectingComponentEditors(globalRect);
            if (!intersections.empty())
            {
                SelectRangeOfComponentEditors(m_componentEditorLastSelectedIndex, GetComponentEditorIndex(intersections.front()), true);
            }
        }
        else
        {
            ClearComponentEditorSelection();
            SelectIntersectingComponentEditors(globalRect, true);
        }
    }

    UpdateInternalState();

    //ensure selection logic executes only once per click since eventFilter may execute multiple times for a single click
    m_selectionEventAccepted = true;
    return true;
}

AZ::Entity::ComponentArrayType PropertiesContainer::GetSelectedComponents()
{
    ComponentEditorVector selectedComponentEditors;

    selectedComponentEditors.reserve(m_componentEditors.size());
    for (auto componentEditor : m_componentEditors)
    {
        if (componentEditor->isVisible() && componentEditor->IsSelected())
        {
            selectedComponentEditors.push_back(componentEditor);
        }
    }

    AZ::Entity::ComponentArrayType selectedComponents;

    for (auto componentEditor : selectedComponentEditors)
    {
        const auto& components = componentEditor->GetComponents();
        selectedComponents.insert(selectedComponents.end(), components.begin(), components.end());
    }

    return selectedComponents;
}

void PropertiesContainer::BuildSharedComponentList(ComponentTypeMap& sharedComponentsByType, const AzToolsFramework::EntityIdList& entitiesShown)
{
    // For single selection of a slice-instanced entity, gather the direct slice ancestor
    // so we can visualize per-component differences.
    m_compareToEntity.reset();
    if (1 == entitiesShown.size())
    {
        AZ::SliceComponent::SliceInstanceAddress address;
        EBUS_EVENT_ID_RESULT(address, entitiesShown[0], AzFramework::EntityIdContextQueryBus, GetOwningSlice);
        if (address.IsValid())
        {
            AZ::SliceComponent::EntityAncestorList ancestors;
            address.GetReference()->GetInstanceEntityAncestry(entitiesShown[0], ancestors, 1);

            if (!ancestors.empty())
            {
                m_compareToEntity = AzToolsFramework::SliceUtilities::CloneSliceEntityForComparison(*ancestors[0].m_entity, *address.GetInstance(), *m_serializeContext);
            }
        }
    }

    // Create a SharedComponentInfo for each component
    // that selected entities have in common.
    // See comments on SharedComponentInfo for more details
    for (AZ::EntityId entityId : entitiesShown)
    {
        AZ::Entity* entity = nullptr;
        EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId);
        AZ_Assert(entity, "Entity was selected but no such entity exists?");
        if (!entity)
        {
            continue;
        }

        // Track how many of each component-type we've seen on this entity
        AZStd::unordered_map<AZ::Uuid, size_t> entityComponentCounts;

        for (AZ::Component* component : entity->GetComponents())
        {
            const AZ::Uuid& componentType = azrtti_typeid(component);
            const AZ::SerializeContext::ClassData* classData = m_serializeContext->FindClassData(componentType);

            // Skip components without edit data
            if (!classData || !classData->m_editData)
            {
                continue;
            }

            // Skip components that are set to invisible.
            if (const AZ::Edit::ElementData* editorDataElement = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData))
            {
                if (AZ::Edit::Attribute* visibilityAttribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::Visibility))
                {
                    AzToolsFramework::PropertyAttributeReader reader(component, visibilityAttribute);
                    AZ::u32 visibilityValue;
                    if (reader.Read<AZ::u32>(visibilityValue))
                    {
                        if (visibilityValue == AZ_CRC("PropertyVisibility_Hide", 0x32ab90f7))
                        {
                            continue;
                        }
                    }
                }
            }

            // The sharedComponentList is created based on the first entity.
            if (entityId == *entitiesShown.begin())
            {
                // Add new SharedComponentInfo
                SharedComponentInfo sharedComponent;
                sharedComponent.m_classData = classData;
                sharedComponentsByType[componentType].push_back(sharedComponent);
            }

            // Skip components that don't correspond to a type from the first entity.
            if (sharedComponentsByType.find(componentType) == sharedComponentsByType.end())
            {
                continue;
            }

            // Update entityComponentCounts (may be multiple components of this type)
            auto entityComponentCountsIt = entityComponentCounts.find(componentType);
            size_t componentIndex = (entityComponentCountsIt == entityComponentCounts.end())
                ? 0
                : entityComponentCountsIt->second;
            entityComponentCounts[componentType] = componentIndex + 1;

            // Skip component if the first entity didn't have this many.
            if (componentIndex >= sharedComponentsByType[componentType].size())
            {
                continue;
            }

            // Component accepted! Add it as an instance
            SharedComponentInfo& sharedComponent = sharedComponentsByType[componentType][componentIndex];
            sharedComponent.m_instances.push_back(component);

            // If specified, locate the corresponding component in the comparison entity to
            // visualize differences.
            if (m_compareToEntity && !sharedComponent.m_compareInstance)
            {
                size_t compareComponentIndex = 0;
                for (AZ::Component* compareComponent : m_compareToEntity.get()->GetComponents())
                {
                    const AZ::Uuid& compareComponentType = azrtti_typeid(compareComponent);
                    if (componentType == compareComponentType)
                    {
                        if (componentIndex == compareComponentIndex)
                        {
                            sharedComponent.m_compareInstance = compareComponent;
                            break;
                        }
                        compareComponentIndex++;
                    }
                }
            }
        }
    }

    // Cull any SharedComponentInfo that doesn't fit all our requirements
    ComponentTypeMap::iterator sharedComponentsByTypeIterator = sharedComponentsByType.begin();
    while (sharedComponentsByTypeIterator != sharedComponentsByType.end())
    {
        AZStd::vector<SharedComponentInfo>& sharedComponents = sharedComponentsByTypeIterator->second;

        // Remove component if it doesn't exist on every entity
        AZStd::vector<SharedComponentInfo>::iterator sharedComponentIterator = sharedComponents.begin();
        while (sharedComponentIterator != sharedComponents.end())
        {
            if (sharedComponentIterator->m_instances.size() != entitiesShown.size()
                || sharedComponentIterator->m_instances.empty())
            {
                sharedComponentIterator = sharedComponents.erase(sharedComponentIterator);
            }
            else
            {
                ++sharedComponentIterator;
            }
        }

        // Remove entry if all its components were culled
        if (sharedComponents.size() == 0)
        {
            sharedComponentsByTypeIterator = sharedComponentsByType.erase(sharedComponentsByTypeIterator);
        }
        else
        {
            ++sharedComponentsByTypeIterator;
        }
    }
}

void PropertiesContainer::BuildSharedComponentUI(ComponentTypeMap& sharedComponentsByType, const AzToolsFramework::EntityIdList& entitiesShown)
{
    (void)entitiesShown;

    // At this point in time:
    // - Each SharedComponentInfo should contain one component instance
    //   from each selected entity.
    // - Any pre-existing m_componentEditor entries should be
    //   cleared of component instances.

    // Add each component instance to its corresponding editor
    // We add them in the order that the component factories were registered in, this provides
    // a consistent order of components. It doesn't appear to be the case that components always
    // stay in the order they were added to the entity in, some of our slices do not have the
    // UiElementComponent first for example.
    const AZStd::vector<AZ::Uuid>* componentTypes;
    EBUS_EVENT_RESULT(componentTypes, UiSystemBus, GetComponentTypesForMenuOrdering);

    // There could be components that were not registered for component ordering. We don't
    // want to hide them. So add them at the end of the list
    AZStd::vector<AZ::Uuid> componentOrdering;
    componentOrdering = *componentTypes;
    for (auto sharedComponentMapEntry : sharedComponentsByType)
    {
        if (AZStd::find(componentOrdering.begin(), componentOrdering.end(), sharedComponentMapEntry.first) == componentOrdering.end())
        {
            componentOrdering.push_back(sharedComponentMapEntry.first);
        }
    }

    m_componentEditors.clear();

    for (auto& componentType : componentOrdering)
    {
        if (sharedComponentsByType.count(componentType) <= 0)
        {
            continue; // there are no components of this type in the sharedComponentsByType map
        }

        const auto& sharedComponents = sharedComponentsByType[componentType];

        for (size_t sharedComponentIndex = 0; sharedComponentIndex < sharedComponents.size(); ++sharedComponentIndex)
        {
            const SharedComponentInfo& sharedComponent = sharedComponents[sharedComponentIndex];

            AZ_Assert(sharedComponent.m_instances.size() == entitiesShown.size()
                && !sharedComponent.m_instances.empty(),
                "sharedComponentsByType should only contain valid entries at this point");

            // Create an editor if necessary
            ComponentEditorVector& componentEditors = m_componentEditorsByType[componentType];
            if (sharedComponentIndex >= componentEditors.size())
            {
                componentEditors.push_back(CreateComponentEditor(*sharedComponent.m_instances[0]));
            }
            else
            {
                // Place existing editor in correct order
                m_rowLayout->removeWidget(componentEditors[sharedComponentIndex]);
                m_rowLayout->addWidget(componentEditors[sharedComponentIndex]);
            }

            AzToolsFramework::ComponentEditor* componentEditor = componentEditors[sharedComponentIndex];

            // Save a list of components in order shown
            m_componentEditors.push_back(componentEditor);

            // Add instances to componentEditor
            auto& componentInstances = sharedComponent.m_instances;
            for (AZ::Component* componentInstance : componentInstances)
            {
                // non-first instances are aggregated under the first instance
                AZ::Component* aggregateInstance = componentInstance != componentInstances.front() ? componentInstances.front() : nullptr;

                // Reference the slice entity if we are a slice so we can indicate differences from base
                AZ::Component* compareInstance = sharedComponent.m_compareInstance;

                componentEditor->AddInstance(componentInstance, aggregateInstance, compareInstance);
            }

            // Refresh editor
            componentEditor->InvalidateAll();
            componentEditor->show();
        }
    }
}

AzToolsFramework::ComponentEditor* PropertiesContainer::CreateComponentEditor(const AZ::Component& componentInstance)
{
    AzToolsFramework::ComponentEditor* editor = new AzToolsFramework::ComponentEditor(m_serializeContext, m_propertiesWidget, this);
    connect(editor, &AzToolsFramework::ComponentEditor::OnDisplayComponentEditorMenu, this, &PropertiesContainer::OnDisplayUiComponentEditorMenu);
    
    m_rowLayout->addWidget(editor);
    editor->hide();

    return editor;
}

bool PropertiesContainer::DoesOwnFocus() const
{
    QWidget* widget = QApplication::focusWidget();
    return this == widget || isAncestorOf(widget);
}

QRect PropertiesContainer::GetWidgetGlobalRect(const QWidget* widget) const
{
    return QRect(
        widget->mapToGlobal(widget->rect().topLeft()),
        widget->mapToGlobal(widget->rect().bottomRight()));
}

bool PropertiesContainer::DoesIntersectWidget(const QRect& globalRect, const QWidget* widget) const
{
    return widget->isVisible() && globalRect.intersects(GetWidgetGlobalRect(widget));
}

bool PropertiesContainer::DoesIntersectSelectedComponentEditor(const QRect& globalRect) const
{
    for (auto componentEditor : GetIntersectingComponentEditors(globalRect))
    {
        if (componentEditor->IsSelected())
        {
            return true;
        }
    }
    return false;
}

bool PropertiesContainer::DoesIntersectNonSelectedComponentEditor(const QRect& globalRect) const
{
    for (auto componentEditor : GetIntersectingComponentEditors(globalRect))
    {
        if (!componentEditor->IsSelected())
        {
            return true;
        }
    }
    return false;
}

void PropertiesContainer::ClearComponentEditorSelection()
{
    AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
    for (auto componentEditor : m_componentEditors)
    {
        componentEditor->SetSelected(false);
    }

    UpdateInternalState();
}

void PropertiesContainer::SelectRangeOfComponentEditors(const AZ::s32 index1, const AZ::s32 index2, bool selected)
{
    if (index1 >= 0 && index2 >= 0)
    {
        const AZ::s32 min = AZStd::min(index1, index2);
        const AZ::s32 max = AZStd::max(index1, index2);
        for (AZ::s32 index = min; index <= max; ++index)
        {
            m_componentEditors[index]->SetSelected(selected);
        }
        m_componentEditorLastSelectedIndex = index2;
    }

    UpdateInternalState();
}

void PropertiesContainer::SelectIntersectingComponentEditors(const QRect& globalRect, bool selected)
{
    for (auto componentEditor : GetIntersectingComponentEditors(globalRect))
    {
        componentEditor->SetSelected(selected);
        m_componentEditorLastSelectedIndex = GetComponentEditorIndex(componentEditor);
    }

    UpdateInternalState();
}

void PropertiesContainer::ToggleIntersectingComponentEditors(const QRect& globalRect)
{
    for (auto componentEditor : GetIntersectingComponentEditors(globalRect))
    {
        componentEditor->SetSelected(!componentEditor->IsSelected());
        m_componentEditorLastSelectedIndex = GetComponentEditorIndex(componentEditor);
    }

    UpdateInternalState();
}

AZ::s32 PropertiesContainer::GetComponentEditorIndex(const AzToolsFramework::ComponentEditor* componentEditor) const
{
    AZ::s32 index = 0;
    for (auto componentEditorToCompare : m_componentEditors)
    {
        if (componentEditorToCompare == componentEditor)
        {
            return index;
        }
        ++index;
    }
    return -1;
}

AZStd::vector<AzToolsFramework::ComponentEditor*> PropertiesContainer::GetIntersectingComponentEditors(const QRect& globalRect) const
{
    ComponentEditorVector intersectingComponentEditors;
    intersectingComponentEditors.reserve(m_componentEditors.size());
    for (auto componentEditor : m_componentEditors)
    {
        if (DoesIntersectWidget(globalRect, componentEditor))
        {
            intersectingComponentEditors.push_back(componentEditor);
        }
    }
    return intersectingComponentEditors;
}

void PropertiesContainer::CreateActions()
{
    QAction* seperator1 = new QAction(this);
    seperator1->setSeparator(true);
    addAction(seperator1);

    m_actionToAddComponents = new QAction(tr("Add component"), this);
    m_actionToAddComponents->setShortcutContext(Qt::WidgetWithChildrenShortcut);
    connect(m_actionToAddComponents, &QAction::triggered, this, &PropertiesContainer::OnAddComponent);
    addAction(m_actionToAddComponents);

    m_actionToDeleteComponents = ComponentHelpers::CreateRemoveComponentsAction(this);
    addAction(m_actionToDeleteComponents);

    QAction* seperator2 = new QAction(this);
    seperator2->setSeparator(true);
    addAction(seperator2);

    m_actionToCutComponents = ComponentHelpers::CreateCutComponentsAction(this);
    addAction(m_actionToCutComponents);

    m_actionToCopyComponents = ComponentHelpers::CreateCopyComponentsAction(this);
    addAction(m_actionToCopyComponents);

    m_actionToPasteComponents = ComponentHelpers::CreatePasteComponentsAction(this);
    addAction(m_actionToPasteComponents);

    UpdateInternalState();
}

void PropertiesContainer::UpdateActions()
{
    ComponentHelpers::UpdateRemoveComponentsAction(m_actionToDeleteComponents);
    ComponentHelpers::UpdateCutComponentsAction(m_actionToCutComponents);
    ComponentHelpers::UpdateCopyComponentsAction(m_actionToCopyComponents);
    // The paste components action always remains enabled except for when the context menu is up
    // This is so a paste can be performed after a copy operation was made via the shortcut keys (since we don't know when a copy was performed)
}

void PropertiesContainer::UpdateOverlay()
{
    m_overlay->setVisible(true);
    m_overlay->setGeometry(m_componentListContents->rect());
    m_overlay->raise();
    m_overlay->update();
}

void PropertiesContainer::UpdateInternalState()
{
    UpdateActions();
    UpdateOverlay();
}

void PropertiesContainer::OnAddComponent()
{
    HierarchyMenu contextMenu(m_editorWindow->GetHierarchy(),
        HierarchyMenu::Show::kAddComponents,
        true);

    contextMenu.exec(QCursor::pos());
}

void PropertiesContainer::OnDisplayUiComponentEditorMenu(const QPoint& position)
{
    ShowContextMenu(position);
}

void PropertiesContainer::ShowContextMenu(const QPoint& position)
{
    UpdateInternalState();
    // The paste components action is only updated right before the context menu appears, otherwise it remains enabled
    ComponentHelpers::UpdatePasteComponentsAction(m_actionToPasteComponents);

    HierarchyMenu contextMenu(m_editorWindow->GetHierarchy(),
        HierarchyMenu::Show::kPushToSlice,
        false);

    contextMenu.addActions(actions());

    if (!contextMenu.isEmpty())
    {
        contextMenu.exec(position);
    }

    // Keep the paste components action enabled when the context menu is not showing in order to handle pastes after a copy was performed
    m_actionToPasteComponents->setEnabled(true);
}

void PropertiesContainer::Update()
{
    size_t selectedEntitiesAmount = m_selectedEntities.size();
    QString displayName;

    if (selectedEntitiesAmount == 0)
    {
        displayName = "No Canvas Loaded";
    }
    else if (selectedEntitiesAmount == 1)
    {
        // Either only one element selected, or none (still is 1 because it selects the canvas instead)

        // If the canvas was selected
        if (m_isCanvasSelected)
        {
            displayName = "Canvas";
        }
        // Else one element was selected
        else
        {
            // Set the name in the properties pane to the name of the element
            AZ::EntityId selectedElement = m_selectedEntities.front();
            AZStd::string selectedEntityName;
            EBUS_EVENT_ID_RESULT(selectedEntityName, selectedElement, UiElementBus, GetName);
            displayName = selectedEntityName.c_str();
        }
    }
    else // more than one entity selected
    {
        displayName = ToString(selectedEntitiesAmount) + " elements selected";
    }

    // Update the selected element display name
    if (m_selectedEntityDisplayNameWidget != nullptr)
    {
        m_selectedEntityDisplayNameWidget->setText(displayName);

        // Preventing renaming entities if the canvas entity is selected or
        // multiple entities are selected.
        if (m_isCanvasSelected || selectedEntitiesAmount > 1)
        {
            m_selectedEntityDisplayNameWidget->setEnabled(false);
        }
        else
        {
            m_selectedEntityDisplayNameWidget->setEnabled(true);
        }
    }

    // Clear content.
    {
        for (int j = m_rowLayout->count(); j > 0; --j)
        {
            AzToolsFramework::ComponentEditor* editor = static_cast<AzToolsFramework::ComponentEditor*>(m_rowLayout->itemAt(j - 1)->widget());

            editor->hide();
            editor->ClearInstances(true);
        }

        m_compareToEntity.reset();
    }

    UpdateEditorOnlyCheckbox();

    if (m_selectedEntities.empty())
    {
        return; // nothing to do
    }

    ComponentTypeMap sharedComponentList;
    BuildSharedComponentList(sharedComponentList, m_selectedEntities);
    BuildSharedComponentUI(sharedComponentList, m_selectedEntities);

    UpdateInternalState();
}

void PropertiesContainer::UpdateEditorOnlyCheckbox()
{
    if (m_isCanvasSelected)
    {
        // The canvas itself can't be editor-only, so don't show the checkbox when the
        // canvas is displayed in the properties pane.
        m_editorOnlyCheckbox->setVisible(false);
    }
    else
    {
        QSignalBlocker noSignals(m_editorOnlyCheckbox);

        if (m_selectedEntities.empty())
        {
            m_editorOnlyCheckbox->setVisible(false);
            return;
        }

        m_editorOnlyCheckbox->setVisible(true);

        bool allEditorOnly = true;
        bool noneEditorOnly = true;

        for (AZ::EntityId id : m_selectedEntities)
        {
            // If any of the entities is a slice root, grey out the checkbox.
            bool isSliceRoot = false;
            AzToolsFramework::EditorEntityInfoRequestBus::EventResult(isSliceRoot, id, &AzToolsFramework::EditorEntityInfoRequestBus::Events::IsSliceRoot);
            if (isSliceRoot)
            {
                m_editorOnlyCheckbox->setChecked(false);
                m_editorOnlyCheckbox->setEnabled(false);
                return;
            }

            bool isEditorOnly = false;
            AzToolsFramework::EditorOnlyEntityComponentRequestBus::EventResult(isEditorOnly, id, &AzToolsFramework::EditorOnlyEntityComponentRequests::IsEditorOnlyEntity);

            allEditorOnly &= isEditorOnly;
            noneEditorOnly &= !isEditorOnly;
        }

        m_editorOnlyCheckbox->setEnabled(true);

        if (allEditorOnly)
        {
            m_editorOnlyCheckbox->setCheckState(Qt::CheckState::Checked);
        }
        else if (noneEditorOnly)
        {
            m_editorOnlyCheckbox->setCheckState(Qt::CheckState::Unchecked);
        }
        else // Some marked editor-only, some not
        {
            m_editorOnlyCheckbox->setCheckState(Qt::CheckState::PartiallyChecked);
        }
    }
}

void PropertiesContainer::Refresh(AzToolsFramework::PropertyModificationRefreshLevel refreshLevel, const AZ::Uuid* componentType)
{
    if (m_selectionHasChanged)
    {
        Update();
        m_selectionHasChanged = false;
    }
    else
    {
        for (auto& componentEditorsPair : m_componentEditorsByType)
        {
            if (!componentType || (*componentType == componentEditorsPair.first))
            {
                for (AzToolsFramework::ComponentEditor* editor : componentEditorsPair.second)
                {
                    if (editor->isVisible())
                    {
                        editor->QueuePropertyEditorInvalidation(refreshLevel);
                    }
                }
            }
        }

        // If the selection has not changed, but a refresh was prompted then the name of the currently selected entity might
        // have changed.
        size_t selectedEntitiesAmount = m_selectedEntities.size();
        // Check if only one entity is selected and that it is an element
        if (selectedEntitiesAmount == 1 && !m_isCanvasSelected)
        {
            // Set the name in the properties pane to the name of the element
            AZ::EntityId selectedElement = m_selectedEntities.front();
            AZStd::string selectedEntityName;
            EBUS_EVENT_ID_RESULT(selectedEntityName, selectedElement, UiElementBus, GetName);

            // Update the selected element display name
            if (m_selectedEntityDisplayNameWidget != nullptr)
            {
                m_selectedEntityDisplayNameWidget->setText(selectedEntityName.c_str());
            }
        }
    }
}

void PropertiesContainer::SelectionChanged(HierarchyItemRawPtrList* items)
{
    ClearComponentEditorSelection();

    m_selectedEntities.clear();
    if (items)
    {
        for (auto i : *items)
        {
            m_selectedEntities.push_back(i->GetEntityId());
        }
    }

    m_isCanvasSelected = false;

    if (m_selectedEntities.empty())
    {
        // Add the canvas
        AZ::EntityId canvasId = m_editorWindow->GetCanvas();
        if (canvasId.IsValid())
        {
            m_selectedEntities.push_back(canvasId);
            m_isCanvasSelected = true;
        }
    }

    m_selectionHasChanged = true;
}

void PropertiesContainer::SelectedEntityPointersChanged()
{
    m_selectionHasChanged = true;
    Refresh();
}

void PropertiesContainer::RequestPropertyContextMenu(AzToolsFramework::InstanceDataNode* node, const QPoint& globalPos)
{
    ShowContextMenu(globalPos);
}

void PropertiesContainer::SetSelectedEntityDisplayNameWidget(QLineEdit* selectedEntityDisplayNameWidget)
{
    if (selectedEntityDisplayNameWidget)
    {
        if (m_selectedEntityDisplayNameWidget)
        {
            QObject::disconnect(m_selectedEntityDisplayNameWidget);
        }

        m_selectedEntityDisplayNameWidget = selectedEntityDisplayNameWidget;

        // Listen for changes to the line edit field
        QObject::connect(m_selectedEntityDisplayNameWidget,
            &QLineEdit::editingFinished,
            [this]()
            {
                // Ignore editing when this field is read-only or if there is more than one
                // entity selected.
                if (!m_selectedEntityDisplayNameWidget->isEnabled() || m_selectedEntities.size() != 1)
                {
                    return;
                }

                AZ::EntityId selectedEntityId = m_selectedEntities.front();
                AZ::Entity* selectedEntity = nullptr;
                EBUS_EVENT_RESULT(selectedEntity, AZ::ComponentApplicationBus, FindEntity, selectedEntityId);
                if (selectedEntity)
                {
                    AZStd::string currentName = selectedEntity->GetName();
                    AZStd::string newName = m_selectedEntityDisplayNameWidget->text().toUtf8().constData();

                    CommandHierarchyItemRename::Push(m_editorWindow->GetActiveStack(),
                        m_editorWindow->GetHierarchy(),
                        selectedEntityId,
                        currentName.c_str(),
                        newName.c_str());
                }
            });
    }
}

void PropertiesContainer::SetEditorOnlyCheckbox(QCheckBox* editorOnlyCheckbox)
{
    m_editorOnlyCheckbox = editorOnlyCheckbox;

    QObject::connect(m_editorOnlyCheckbox,
        &QCheckBox::stateChanged,
        [this](int value)
        {
            QSignalBlocker blocker(this);

            QMetaObject::invokeMethod(m_editorWindow->GetHierarchy(), "SetEditorOnlyForSelectedItems", Qt::QueuedConnection, Q_ARG(bool, value));
        }
    );
}

#include <PropertiesContainer.moc>