/*
* 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 "ProductAssetDetailsPanel.h"

#include "AssetTreeFilterModel.h"
#include "GoToButton.h"
#include "ProductAssetTreeItemData.h"
#include "native/utilities/assetUtils.h"
#include "native/utilities/MissingDependencyScanner.h"

#include <AssetDatabase/AssetDatabase.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>

#include <native/ui/ui_GoToButton.h>
#include <native/ui/ui_ProductAssetDetailsPanel.h>
#include <QDateTime>
#include <QDesktopServices>
#include <QDir>
#include <QHBoxLayout>
#include <QItemSelection>
#include <QStringLiteral>
#include <QUrl>

namespace AssetProcessor
{

    ProductAssetDetailsPanel::ProductAssetDetailsPanel(QWidget* parent) : AssetDetailsPanel(parent), m_ui(new Ui::ProductAssetDetailsPanel)
    {
        m_ui->setupUi(this);
        m_ui->scrollAreaWidgetContents->setLayout(m_ui->scrollableVerticalLayout);
        m_ui->MissingProductDependenciesTable->setColumnWidth(1, 160);
        ResetText();
        connect(m_ui->MissingProductDependenciesSupport, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnSupportClicked);
        connect(m_ui->ScanMissingDependenciesButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnScanFileClicked);
        connect(m_ui->ScanFolderButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnScanFolderClicked);

        connect(m_ui->ClearMissingDependenciesButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnClearScanFileClicked);
        connect(m_ui->ClearScanFolderButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnClearScanFolderClicked);
    }

    ProductAssetDetailsPanel::~ProductAssetDetailsPanel()
    {

    }

    void ProductAssetDetailsPanel::SetScanQueueEnabled(bool enabled)
    {
        // Don't change state if it's already the same.
        if (m_ui->ScanMissingDependenciesButton->isEnabled() == enabled)
        {
            return;
        }
        m_ui->ScanMissingDependenciesButton->setEnabled(enabled);
        m_ui->ScanFolderButton->setEnabled(enabled);

        if (enabled)
        {
            m_ui->ScanMissingDependenciesButton->setToolTip(tr("Scans this file for missing dependencies. This may take some time."));
            m_ui->ScanFolderButton->setToolTip(tr("Scans all files in this folder and subfolders for missing dependencies. This may take some time."));
        }
        else
        {
            QString disabledTooltip(tr("Scanning disabled until asset processing completes."));
            m_ui->ScanMissingDependenciesButton->setToolTip(disabledTooltip);
            m_ui->ScanFolderButton->setToolTip(disabledTooltip);
        }
    }

    void ProductAssetDetailsPanel::AssetDataSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/)
    {
        // Even if multi-select is enabled, only display the first selected item.
        if (selected.indexes().count() == 0 || !selected.indexes()[0].isValid())
        {
            ResetText();
            return;
        }

        QModelIndex productModelIndex = m_productFilterModel->mapToSource(selected.indexes()[0]);

        if (!productModelIndex.isValid())
        {
            return;
        }
        m_currentItem = static_cast<AssetTreeItem*>(productModelIndex.internalPointer());
        RefreshUI();
    }

    void ProductAssetDetailsPanel::RefreshUI()
    {
        const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
        m_ui->assetNameLabel->setText(m_currentItem->GetData()->m_name);

        if (m_currentItem->GetData()->m_isFolder || !productItemData)
        {
            // Folders don't have details.
            SetDetailsVisible(false);
            return;
        }
        SetDetailsVisible(true);

        AZ::Data::AssetId assetId;

        m_assetDatabaseConnection->QuerySourceByProductID(
            productItemData->m_databaseInfo.m_productID,
            [&](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry)
        {
            assetId = AZ::Data::AssetId(sourceEntry.m_sourceGuid, productItemData->m_databaseInfo.m_subID);
            // Use a decimal value to display the sub ID and not hex. Lumberyard is not consistent about
            // how sub IDs are displayed, so it's important to double check what format a sub ID is in before using it elsewhere.
            m_ui->productAssetIdValueLabel->setText(assetId.ToString<AZStd::string>(AZ::Data::AssetId::SubIdDisplayType::Decimal).c_str());

            // Make sure this is the only connection to the button.
            m_ui->gotoAssetButton->m_ui->goToPushButton->disconnect();

            connect(m_ui->gotoAssetButton->m_ui->goToPushButton, &QPushButton::clicked, [=] {
                GoToSource(sourceEntry.m_sourceName);
            });

            m_ui->sourceAssetValueLabel->setText(sourceEntry.m_sourceName.c_str());
            return true;
        });

        AZStd::string platform;

        m_assetDatabaseConnection->QueryJobByProductID(
            productItemData->m_databaseInfo.m_productID,
            [&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
        {
            QDateTime lastTimeProcessed = QDateTime::fromMSecsSinceEpoch(jobEntry.m_lastLogTime);
            m_ui->lastTimeProcessedValueLabel->setText(lastTimeProcessed.toString());

            m_ui->jobKeyValueLabel->setText(jobEntry.m_jobKey.c_str());
            platform = jobEntry.m_platform;
            m_ui->platformValueLabel->setText(jobEntry.m_platform.c_str());
            return true;
        });

        BuildOutgoingProductDependencies(productItemData, platform);
        BuildIncomingProductDependencies(productItemData, assetId, platform);
        BuildMissingProductDependencies(productItemData);
    }

    void ProductAssetDetailsPanel::BuildOutgoingProductDependencies(
        const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData,
        const AZStd::string& platform)
    {
        // Clear & ClearContents leave the table dimensions the same, so set rowCount to zero to reset it.
        m_ui->outgoingProductDependenciesTable->setRowCount(0);

        m_ui->outgoingUnmetPathProductDependenciesList->clear();
        int productDependencyCount = 0;
        int productPathDependencyCount = 0;
        m_assetDatabaseConnection->QueryProductDependencyByProductId(
            productItemData->m_databaseInfo.m_productID,
            [&](AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& dependency)
        {
            if (!dependency.m_dependencySourceGuid.IsNull())
            {
                m_assetDatabaseConnection->QueryProductBySourceGuidSubID(
                    dependency.m_dependencySourceGuid,
                    dependency.m_dependencySubID,
                    [&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product)
                {
                    bool platformMatches = false;

                    m_assetDatabaseConnection->QueryJobByJobID(
                        product.m_jobPK,
                        [&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
                    {
                        if (platform.compare(jobEntry.m_platform) == 0)
                        {
                            platformMatches = true;
                        }
                        return true;
                    });

                    if (platformMatches)
                    {
                        m_ui->outgoingProductDependenciesTable->insertRow(productDependencyCount);

                        // Qt handles cleanup automatically, setting this as the parent means
                        // when this panel is torn down, these widgets will be destroyed.
                        GoToButton* rowGoToButton = new GoToButton(this);
                        connect(rowGoToButton->m_ui->goToPushButton, &QPushButton::clicked, [=] {
                            GoToProduct(product.m_productName);
                        });
                        m_ui->outgoingProductDependenciesTable->setCellWidget(productDependencyCount, 0, rowGoToButton);

                        QTableWidgetItem* rowName = new QTableWidgetItem(product.m_productName.c_str());
                        m_ui->outgoingProductDependenciesTable->setItem(productDependencyCount, 1, rowName);
                        ++productDependencyCount;
                    }
                    return true;
                });
            }

            // If there is both a path and an asset ID on this dependency, then something has gone wrong.
            // Other tooling should have reported this error. In the UI, show both the asset ID and path.
            if (!dependency.m_unresolvedPath.empty())
            {
                QListWidgetItem* listWidgetItem = new QListWidgetItem();
                listWidgetItem->setText(dependency.m_unresolvedPath.c_str());
                m_ui->outgoingUnmetPathProductDependenciesList->addItem(listWidgetItem);
                ++productPathDependencyCount;
            }
            return true;
        });
        m_ui->outgoingProductDependenciesValueLabel->setText(QString::number(productDependencyCount));
        m_ui->outgoingUnmetPathProductDependenciesValueLabel->setText(QString::number(productPathDependencyCount));

        if (productDependencyCount == 0)
        {
            m_ui->outgoingProductDependenciesTable->insertRow(productDependencyCount);
            QTableWidgetItem* rowName = new QTableWidgetItem(tr("No product dependencies"));
            m_ui->outgoingProductDependenciesTable->setItem(productDependencyCount, 1, rowName);
            ++productDependencyCount;
        }

        if (productPathDependencyCount == 0)
        {
            QListWidgetItem* listWidgetItem = new QListWidgetItem();
            listWidgetItem->setText(tr("No unmet dependencies"));
            m_ui->outgoingUnmetPathProductDependenciesList->addItem(listWidgetItem);
            ++productPathDependencyCount;
        }

        m_ui->outgoingProductDependenciesTable->setMinimumHeight(m_ui->outgoingProductDependenciesTable->rowHeight(0) * productDependencyCount + 2 * m_ui->outgoingProductDependenciesTable->frameWidth());
        m_ui->outgoingProductDependenciesTable->adjustSize();

        m_ui->outgoingUnmetPathProductDependenciesList->setMinimumHeight(m_ui->outgoingUnmetPathProductDependenciesList->sizeHintForRow(0) * productPathDependencyCount + 2 * m_ui->outgoingUnmetPathProductDependenciesList->frameWidth());
        m_ui->outgoingUnmetPathProductDependenciesList->adjustSize();
    }

    void ProductAssetDetailsPanel::BuildIncomingProductDependencies(
        const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData,
        const AZ::Data::AssetId& assetId,
        const AZStd::string& platform)
    {
        // Clear & ClearContents leave the table dimensions the same, so set rowCount to zero to reset it.
        m_ui->incomingProductDependenciesTable->setRowCount(0);

        int incomingProductDependencyCount = 0;
        m_assetDatabaseConnection->QueryDirectReverseProductDependenciesBySourceGuidSubId(
            assetId.m_guid,
            assetId.m_subId,
            [&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& incomingDependency)
        {
            bool platformMatches = false;

            m_assetDatabaseConnection->QueryJobByJobID(
                incomingDependency.m_jobPK,
                [&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
            {
                if (platform.compare(jobEntry.m_platform) == 0)
                {
                    platformMatches = true;
                }
                return true;
            });
            if (platformMatches)
            {
                m_ui->incomingProductDependenciesTable->insertRow(incomingProductDependencyCount);

                GoToButton* rowGoToButton = new GoToButton(this);
                connect(rowGoToButton->m_ui->goToPushButton, &QPushButton::clicked, [=] {
                    GoToProduct(incomingDependency.m_productName);
                });
                m_ui->incomingProductDependenciesTable->setCellWidget(incomingProductDependencyCount, 0, rowGoToButton);

                QTableWidgetItem* rowName = new QTableWidgetItem(incomingDependency.m_productName.c_str());
                m_ui->incomingProductDependenciesTable->setItem(incomingProductDependencyCount, 1, rowName);

                ++incomingProductDependencyCount;
            }
            return true;
        });

        m_ui->incomingProductDependenciesValueLabel->setText(QString::number(incomingProductDependencyCount));

        if (incomingProductDependencyCount == 0)
        {
            m_ui->incomingProductDependenciesTable->insertRow(incomingProductDependencyCount);
            QTableWidgetItem* rowName = new QTableWidgetItem(tr("No incoming product dependencies"));
            m_ui->incomingProductDependenciesTable->setItem(incomingProductDependencyCount, 1, rowName);
            ++incomingProductDependencyCount;
        }
        m_ui->incomingProductDependenciesTable->setMinimumHeight(m_ui->incomingProductDependenciesTable->rowHeight(0) * incomingProductDependencyCount + 2 * m_ui->incomingProductDependenciesTable->frameWidth());
        m_ui->incomingProductDependenciesTable->adjustSize();
    }

    struct MissingDependencyTableInfo
    {
        AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry m_databaseEntry;
        AZStd::string m_missingProductName;
    };

    void ProductAssetDetailsPanel::BuildMissingProductDependencies(
        const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData)
    {
        // Clear & ClearContents leave the table dimensions the same, so set rowCount to zero to reset it.
        m_ui->MissingProductDependenciesTable->setRowCount(0);

        int missingDependencyRowCount = 0;
        int missingDependencyCount = 0;

        // Sort missing dependencies by scan time.
        AZStd::vector<MissingDependencyTableInfo> missingDependenciesByScanTime;

        m_assetDatabaseConnection->QueryMissingProductDependencyByProductId(
            productItemData->m_databaseInfo.m_productID,
            [&](AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry& missingDependency)
        {

            AZStd::string missingProductName;
            m_assetDatabaseConnection->QueryProductBySourceGuidSubID(
                missingDependency.m_dependencySourceGuid,
                missingDependency.m_dependencySubId,
                [&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& missingProduct)
            {
                missingProductName = missingProduct.m_productName;
                return false; // There should only be one matching product, stop looking.
            });

            AZStd::vector<MissingDependencyTableInfo>::iterator insertPosition = AZStd::upper_bound(
                missingDependenciesByScanTime.begin(),
                missingDependenciesByScanTime.end(),
                missingDependency.m_scanTimeSecondsSinceEpoch,
                [](AZ::u64 left, const MissingDependencyTableInfo& right) {
                    return left > right.m_databaseEntry.m_scanTimeSecondsSinceEpoch;
                });
            MissingDependencyTableInfo missingDependencyInfo;
            missingDependencyInfo.m_databaseEntry = missingDependency;
            missingDependencyInfo.m_missingProductName = missingProductName;
            missingDependenciesByScanTime.insert(insertPosition, missingDependencyInfo);
            return true;
        });

        bool hasMissingDependency = false;
        for (const auto& missingDependency : missingDependenciesByScanTime)
        {
            m_ui->MissingProductDependenciesTable->insertRow(missingDependencyRowCount);
            // To track if files have been scanned at all, rows with invalid source guids are added on a
            // scan that had no missing dependencies. Don't show a button for those rows.
            if (!missingDependency.m_databaseEntry.m_dependencySourceGuid.IsNull())
            {
                hasMissingDependency = true;
                ++missingDependencyCount;
                GoToButton* rowGoToButton = new GoToButton(this);
                connect(rowGoToButton->m_ui->goToPushButton, &QPushButton::clicked, [&, missingDependency] {
                    GoToProduct(missingDependency.m_missingProductName);
                });
                m_ui->MissingProductDependenciesTable->setCellWidget(missingDependencyRowCount, 0, rowGoToButton);
            }

            QTableWidgetItem* scanTime = new QTableWidgetItem(missingDependency.m_databaseEntry.m_lastScanTime.c_str());
            m_ui->MissingProductDependenciesTable->setItem(missingDependencyRowCount, 1, scanTime);

            QTableWidgetItem* rowName = new QTableWidgetItem(missingDependency.m_databaseEntry.m_missingDependencyString.c_str());
            m_ui->MissingProductDependenciesTable->setItem(missingDependencyRowCount, 2, rowName);

            ++missingDependencyRowCount;
        }

        m_ui->MissingProductDependenciesValueLabel->setText(QString::number(missingDependencyCount));

        if (missingDependencyRowCount == 0)
        {
            m_ui->MissingProductDependenciesTable->insertRow(missingDependencyRowCount);
            QTableWidgetItem* rowName = new QTableWidgetItem(tr("File has not been scanned."));
            m_ui->MissingProductDependenciesTable->setItem(missingDependencyRowCount, 1, rowName);
            ++missingDependencyRowCount;
        }
        else
        {
            m_ui->missingDependencyErrorIcon->setVisible(hasMissingDependency);
        }

        m_ui->MissingProductDependenciesTable->setMinimumHeight(m_ui->MissingProductDependenciesTable->rowHeight(0) * missingDependencyRowCount + 2 * m_ui->MissingProductDependenciesTable->frameWidth());
        m_ui->MissingProductDependenciesTable->adjustSize();
    }

    void ProductAssetDetailsPanel::ResetText()
    {
        m_ui->assetNameLabel->setText(tr("Select an asset to see details"));
        SetDetailsVisible(false);
    }

    void ProductAssetDetailsPanel::SetDetailsVisible(bool visible)
    {
        // The folder selected description has opposite visibility from everything else.
        m_ui->folderSelectedDescription->setVisible(!visible);
        m_ui->ScanFolderButton->setVisible(!visible);
        m_ui->ClearScanFolderButton->setVisible(!visible);
        m_ui->MissingProductDependenciesFolderTitleLabel->setVisible(!visible);

        m_ui->productAssetIdTitleLabel->setVisible(visible);
        m_ui->productAssetIdValueLabel->setVisible(visible);

        m_ui->lastTimeProcessedTitleLabel->setVisible(visible);
        m_ui->lastTimeProcessedValueLabel->setVisible(visible);

        m_ui->jobKeyTitleLabel->setVisible(visible);
        m_ui->jobKeyValueLabel->setVisible(visible);

        m_ui->platformTitleLabel->setVisible(visible);
        m_ui->platformValueLabel->setVisible(visible);

        m_ui->sourceAssetTitleLabel->setVisible(visible);
        m_ui->sourceAssetValueLabel->setVisible(visible);
        m_ui->gotoAssetButton->setVisible(visible);

        m_ui->outgoingProductDependenciesTitleLabel->setVisible(visible);
        m_ui->outgoingProductDependenciesValueLabel->setVisible(visible);
        m_ui->outgoingProductDependenciesTable->setVisible(visible);

        m_ui->outgoingUnmetPathProductDependenciesTitleLabel->setVisible(visible);
        m_ui->outgoingUnmetPathProductDependenciesValueLabel->setVisible(visible);
        m_ui->outgoingUnmetPathProductDependenciesList->setVisible(visible);

        m_ui->incomingProductDependenciesTitleLabel->setVisible(visible);
        m_ui->incomingProductDependenciesValueLabel->setVisible(visible);
        m_ui->incomingProductDependenciesTable->setVisible(visible);

        m_ui->MissingProductDependenciesTitleLabel->setVisible(visible);
        m_ui->MissingProductDependenciesValueLabel->setVisible(visible);
        m_ui->MissingProductDependenciesTable->setVisible(visible);
        m_ui->MissingProductDependenciesSupport->setVisible(visible);
        m_ui->ScanMissingDependenciesButton->setVisible(visible);
        m_ui->ClearMissingDependenciesButton->setVisible(visible);

        m_ui->DependencySeparatorLine->setVisible(visible);

        m_ui->missingDependencyErrorIcon->setVisible(false);
    }

    void ProductAssetDetailsPanel::OnSupportClicked(bool /*checked*/)
    {
        QDesktopServices::openUrl(
            QStringLiteral("https://docs.aws.amazon.com/lumberyard/latest/userguide/asset-bundler-assets-resolving.html"));
    }


    void ProductAssetDetailsPanel::OnScanFileClicked(bool /*checked*/)
    {
        const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
        ScanFileForMissingDependencies(productItemData->m_name, productItemData);
    }

    void ProductAssetDetailsPanel::ScanFileForMissingDependencies(QString scanName, const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData)
    {
        // If the file is already in the queue to scan, don't add it.
        if (m_productIdToScanName.contains(productItemData->m_databaseInfo.m_productID))
        {
            return;
        }
        AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer existingDependencies;
        m_assetDatabaseConnection->QueryProductDependencyByProductId(
            productItemData->m_databaseInfo.m_productID,
            [&](AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry)
        {
            existingDependencies.push_back();
            existingDependencies.back() = AZStd::move(entry);
            return true; // return true to keep iterating over further rows.
        });

        QDir cacheRootDir;
        AssetUtilities::ComputeProjectCacheRoot(cacheRootDir);
        QString pathOnDisk = cacheRootDir.filePath(productItemData->m_databaseInfo.m_productName.c_str());

        AddProductIdToScanCount(productItemData->m_databaseInfo.m_productID, scanName);

        // Run the scan on another thread so the UI remains responsive.
        AZStd::thread scanningThread = AZStd::thread([=]() {
            MissingDependencyScannerRequestBus::Broadcast(&MissingDependencyScannerRequestBus::Events::ScanFile,
                pathOnDisk.toUtf8().constData(),
                MissingDependencyScanner::DefaultMaxScanIteration,
                productItemData->m_databaseInfo.m_productID,
                existingDependencies,
                m_assetDatabaseConnection,
                /*queueDbCommandsOnMainThread*/ true,
                [=](AZStd::string /*relativeDependencyFilePath*/) {
                RemoveProductIdFromScanCount(productItemData->m_databaseInfo.m_productID, scanName);
                // The MissingDependencyScannerRequestBus callback always runs on the main thread, so no need to queue again.
                AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Broadcast(
                    &AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Events::OnProductFileChanged, productItemData->m_databaseInfo);
                if (m_currentItem)
                {
                    // Refresh the UI if the scan that just finished is selected.
                    const AZStd::shared_ptr<const ProductAssetTreeItemData> currentItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
                    if (currentItemData == productItemData)
                    {
                        RefreshUI();
                    }
                }
            });
        });
        scanningThread.detach();
    }

    void ProductAssetDetailsPanel::AddProductIdToScanCount(AZ::s64 scannedProductId, QString scanName)
    {
        AZStd::lock_guard<AZStd::recursive_mutex> lock(m_scanCountMutex);
        m_productIdToScanName.insert(scannedProductId, scanName);
        QHash<QString, MissingDependencyScanGUIInfo>::iterator scanNameIter = m_scanNameToScanGUIInfo.find(scanName);
        if (scanNameIter == m_scanNameToScanGUIInfo.end())
        {
            MissingDependencyScanGUIInfo scanGUIInfo;
            scanGUIInfo.m_scanWidgetRow = new QListWidgetItem();
            scanGUIInfo.m_scanTimeStart = QDateTime::currentDateTime();
            if (m_missingDependencyScanResults)
            {
                m_missingDependencyScanResults->addItem(scanGUIInfo.m_scanWidgetRow);
                // New items are added to the bottom, scroll to them when they are added.
                m_missingDependencyScanResults->scrollToBottom();
            }
            scanNameIter = m_scanNameToScanGUIInfo.insert(scanName, scanGUIInfo);
        }

        // Update the remaining file count for this scan.
        scanNameIter.value().m_remainingFiles++;

        UpdateScannerUI(scanNameIter.value(), scanName);
    }

    void ProductAssetDetailsPanel::RemoveProductIdFromScanCount(AZ::s64 scannedProductId, QString scanName)
    {
        AZStd::lock_guard<AZStd::recursive_mutex> lock(m_scanCountMutex);
        m_productIdToScanName.remove(scannedProductId);
        QHash<QString, MissingDependencyScanGUIInfo>::iterator scanNameIter = m_scanNameToScanGUIInfo.find(scanName);
        if (scanNameIter != m_scanNameToScanGUIInfo.end())
        {
            // Update the remaining file count for this scan.
            scanNameIter.value().m_remainingFiles--;

            UpdateScannerUI(scanNameIter.value(), scanName);
            if (scanNameIter.value().m_remainingFiles <= 0)
            {
                m_scanNameToScanGUIInfo.remove(scanName);
            }
        }
    }

    void ProductAssetDetailsPanel::UpdateScannerUI(MissingDependencyScanGUIInfo& scannerUIInfo, QString scanName)
    {
        if (scannerUIInfo.m_scanWidgetRow == nullptr)
        {
            return;
        }
        if (scannerUIInfo.m_remainingFiles == 0)
        {
            qint64 scanTimeInSeconds = scannerUIInfo.m_scanTimeStart.secsTo(QDateTime::currentDateTime());
            scannerUIInfo.m_scanWidgetRow->setText(tr("Completed scanning %1 in %2 seconds").
                arg(scanName).
                arg(scanTimeInSeconds));
        }
        else
        {
            scannerUIInfo.m_scanWidgetRow->setText(tr("%1: Scanning %2 files for %3").
                arg(QLocale::system().toString(scannerUIInfo.m_scanTimeStart, QLocale::ShortFormat)).
                arg(scannerUIInfo.m_remainingFiles).
                arg(scanName));
        }
    }

    void ProductAssetDetailsPanel::OnScanFolderClicked(bool /*checked*/)
    {
        if (!m_currentItem)
        {
            return;
        }
        ScanFolderForMissingDependencies(m_currentItem->GetData()->m_name, *m_currentItem);
    }

    void ProductAssetDetailsPanel::ScanFolderForMissingDependencies(QString scanName, AssetTreeItem& folder)
    {
        for (int childIndex = 0; childIndex < folder.getChildCount(); ++childIndex)
        {
            AssetTreeItem* child = folder.GetChild(childIndex);
            if (child->GetData()->m_isFolder)
            {
                ScanFolderForMissingDependencies(scanName, *child);
            }
            else
            {
                const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(child->GetData());
                ScanFileForMissingDependencies(scanName, productItemData);
            }
        }
    }

    void ProductAssetDetailsPanel::OnClearScanFileClicked(bool /*checked*/)
    {
        const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
        ClearMissingDependenciesForFile(productItemData);
    }

    void ProductAssetDetailsPanel::OnClearScanFolderClicked(bool /*checked*/)
    {
        if (!m_currentItem)
        {
            return;
        }
        ClearMissingDependenciesForFolder(*m_currentItem);
    }

    void ProductAssetDetailsPanel::ClearMissingDependenciesForFile(const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData)
    {
        m_assetDatabaseConnection->DeleteMissingProductDependencyByProductId(productItemData->m_databaseInfo.m_productID);
        AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Broadcast(
            &AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Events::OnProductFileChanged, productItemData->m_databaseInfo);

        const AZStd::shared_ptr<const ProductAssetTreeItemData> currentItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
        if (currentItemData == productItemData)
        {
            RefreshUI();
        }
    }

    void ProductAssetDetailsPanel::ClearMissingDependenciesForFolder(AssetTreeItem& folder)
    {
        for (int childIndex = 0; childIndex < folder.getChildCount(); ++childIndex)
        {
            AssetTreeItem* child = folder.GetChild(childIndex);
            if (child->GetData()->m_isFolder)
            {
                ClearMissingDependenciesForFolder(*child);
            }
            else
            {
                const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(child->GetData());
                ClearMissingDependenciesForFile(productItemData);
            }
        }
    }
}

#include <native/ui/ProductAssetDetailsPanel.moc>