#include "QQuickTreeViewEx.h"
#include "QQuickTreeViewExPrivate.h"
#include <QtCore/qobject.h>
#include <QtQml/qqmlcontext.h>
#include <QtQuick/private/qquicktaphandler_p.h>
#include <QtQmlModels/private/qqmltreemodeltotablemodel_p_p.h>

// Hard-code the tree column to be 0 for now
static const int kTreeColumn = 0;


QQuickTreeViewExPrivate::QQuickTreeViewExPrivate()
    : QQuickTableViewPrivate()
{
}

QQuickTreeViewExPrivate::~QQuickTreeViewExPrivate()
{
}

QVariant QQuickTreeViewExPrivate::modelImpl() const
{
    return m_assignedModel;
}

void QQuickTreeViewExPrivate::setModelImpl(const QVariant& newModel)
{
    Q_Q(QQuickTreeViewEx);
 
    if (newModel == m_assignedModel)
        return;

    m_assignedModel = newModel;
    QVariant effectiveModel = m_assignedModel;
    if (effectiveModel.userType() == qMetaTypeId<QJSValue>())
        effectiveModel = effectiveModel.value<QJSValue>().toVariant();

    if (effectiveModel.isNull())
        m_treeModelToTableModel.setModel(nullptr);
    else if (const auto qaim = qvariant_cast<QAbstractItemModel*>(effectiveModel))
        m_treeModelToTableModel.setModel(qaim);
    else
        qmlWarning(q) << "TreeView only accepts a model of type QAbstractItemModel";


    scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
    emit q->modelChanged();
}

void QQuickTreeViewExPrivate::initItemCallback(int serializedModelIndex, QObject* object)
{
	Q_Q(QQuickTreeViewEx);
    updateRequiredProperties(serializedModelIndex, object, true);
    QQuickTableViewPrivate::initItemCallback(serializedModelIndex, object);
}

void QQuickTreeViewExPrivate::itemReusedCallback(int serializedModelIndex, QObject* object)
{
    updateRequiredProperties(serializedModelIndex, object, false);
    QQuickTableViewPrivate::itemReusedCallback(serializedModelIndex, object);
}

void QQuickTreeViewExPrivate::dataChangedCallback(
    const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
{
    Q_Q(QQuickTreeViewEx);
    Q_UNUSED(roles);

    for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
        for (int column = topLeft.column(); column <= bottomRight.column(); ++column) {
            const QPoint cell(column, row);
            auto item = q->itemAtCell(cell);
            if (!item)
                continue;

            const int serializedModelIndex = modelIndexAtCell(QPoint(column, row));
            updateRequiredProperties(serializedModelIndex, item, false);
        }
    }
}

void QQuickTreeViewExPrivate::updateRequiredProperties(int serializedModelIndex, QObject* object, bool init)
{
    Q_Q(QQuickTreeViewEx);
    const QPoint cell = cellAtModelIndex(serializedModelIndex);
    const int row = cell.y();
    const int column = cell.x();

    setRequiredProperty("treeView", QVariant::fromValue(q), serializedModelIndex, object, init);
    setRequiredProperty("isTreeNode", column == kTreeColumn, serializedModelIndex, object, init);
    setRequiredProperty("hasChildren", m_treeModelToTableModel.hasChildren(row), serializedModelIndex, object, init);
    setRequiredProperty("expanded", q->isExpanded(row), serializedModelIndex, object, init);
    setRequiredProperty("depth", m_treeModelToTableModel.depthAtRow(row), serializedModelIndex, object, init);
}

void QQuickTreeViewExPrivate::updateSelection(const QRect& oldSelection, const QRect& newSelection)
{
    Q_Q(QQuickTreeViewEx);

    const QRect oldRect = oldSelection.normalized();
    const QRect newRect = newSelection.normalized();

    if (oldSelection == newSelection)
        return;

    // Select the rows inside newRect that doesn't overlap with oldRect
    for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row) {
        if (oldRect.y() != -1 && oldRect.y() <= row && row <= oldRect.y() + oldRect.height())
            continue;
        const QModelIndex startIndex = q->index(row, newRect.x());
        const QModelIndex endIndex = q->index(row, newRect.x() + newRect.width());
        selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select);
    }

    if (oldRect.x() != -1) {
        // Since oldRect is valid, this update is a continuation of an already existing selection!

        // Select the columns inside newRect that don't overlap with oldRect
        for (int column = newRect.x(); column <= newRect.x() + newRect.width(); ++column) {
            if (oldRect.x() <= column && column <= oldRect.x() + oldRect.width())
                continue;
            for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row)
                selectionModel->select(q->index(row, column), QItemSelectionModel::Select);
        }

        // Unselect the rows inside oldRect that don't overlap with newRect
        for (int row = oldRect.y(); row <= oldRect.y() + oldRect.height(); ++row) {
            if (newRect.y() <= row && row <= newRect.y() + newRect.height())
                continue;
            const QModelIndex startIndex = q->index(row, oldRect.x());
            const QModelIndex endIndex = q->index(row, oldRect.x() + oldRect.width());
            selectionModel->select(QItemSelection(startIndex, endIndex), QItemSelectionModel::Deselect);
        }

        // Unselect the columns inside oldRect that don't overlap with newRect
        for (int column = oldRect.x(); column <= oldRect.x() + oldRect.width(); ++column) {
            if (newRect.x() <= column && column <= newRect.x() + newRect.width())
                continue;
            // Since we're not allowed to call select/unselect on the selectionModel with
            // indices from different parents, and since indicies from different parents are
            // expected when working with trees, we need to unselect the indices in the column
            // one by one, rather than the whole column in one go. This, however, can cause a
            // lot of selection fragments in the selectionModel, which eventually can hurt
            // performance. But large selections containing a lot of columns is not normally
            // the case for a treeview, so accept this potential corner case for now.
            for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row)
                selectionModel->select(q->index(row, column), QItemSelectionModel::Deselect);
        }
    }
}

QQuickTreeViewEx::QQuickTreeViewEx(QQuickItem* parent)
    : QQuickTableView(*(new QQuickTreeViewExPrivate), parent)
{
    Q_D(QQuickTreeViewEx);

    setSelectionBehavior(SelectRows);
    setEditTriggers(EditKeyPressed);

    // Note: QQuickTableView will only ever see the table model m_treeModelToTableModel, and
    // never the actual tree model that is assigned to us by the application.
    const auto modelAsVariant = QVariant::fromValue(std::addressof(d->m_treeModelToTableModel));
    d->QQuickTableViewPrivate::setModelImpl(modelAsVariant);
    QObjectPrivate::connect(&d->m_treeModelToTableModel, &QAbstractItemModel::dataChanged,
        d, &QQuickTreeViewExPrivate::dataChangedCallback);
    QObject::connect(&d->m_treeModelToTableModel, &QQmlTreeModelToTableModel::rootIndexChanged,
        this, &QQuickTreeViewEx::rootIndexChanged);

    auto tapHandler = new QQuickTapHandler(this);
    tapHandler->setAcceptedModifiers(Qt::NoModifier);
    connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, tapHandler] {
        if (!pointerNavigationEnabled())
            return;
        if (editTriggers() & DoubleTapped)
            return;

        const int row = cellAtPosition(tapHandler->point().pressPosition()).y();
        toggleExpanded(row);
    });
}

QQuickTreeViewEx::QQuickTreeViewEx(QQuickTreeViewExPrivate& dd, QQuickItem* parent)
    : QQuickTableView(dd, parent)
{
	Q_D(QQuickTreeViewEx);

	setSelectionBehavior(SelectRows);
	setEditTriggers(EditKeyPressed);

	// Note: QQuickTableView will only ever see the table model m_treeModelToTableModel, and
	// never the actual tree model that is assigned to us by the application.
	const auto modelAsVariant = QVariant::fromValue(std::addressof(d->m_treeModelToTableModel));
	d->QQuickTableViewPrivate::setModelImpl(modelAsVariant);
	QObjectPrivate::connect(&d->m_treeModelToTableModel, &QAbstractItemModel::dataChanged,
		d, &QQuickTreeViewExPrivate::dataChangedCallback);
	QObject::connect(&d->m_treeModelToTableModel, &QQmlTreeModelToTableModel::rootIndexChanged,
		this, &QQuickTreeViewEx::rootIndexChanged);

	auto tapHandler = new QQuickTapHandler(this);
	tapHandler->setAcceptedModifiers(Qt::NoModifier);
	connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, tapHandler] {
		if (!pointerNavigationEnabled())
			return;
		if (editTriggers() & DoubleTapped)
			return;

		const int row = cellAtPosition(tapHandler->point().pressPosition()).y();
		toggleExpanded(row);
	});
    d_func()->init();
}

QQuickTreeViewEx::~QQuickTreeViewEx()
{
}

QModelIndex QQuickTreeViewEx::rootIndex() const
{
    return d_func()->m_treeModelToTableModel.rootIndex();
}

void QQuickTreeViewEx::setRootIndex(const QModelIndex& index)
{
    Q_D(QQuickTreeViewEx);
    d->m_treeModelToTableModel.setRootIndex(index);
    positionViewAtCell({ 0, 0 }, QQuickTableView::AlignTop | QQuickTableView::AlignLeft);
}

void QQuickTreeViewEx::resetRootIndex()
{
    Q_D(QQuickTreeViewEx);
    d->m_treeModelToTableModel.resetRootIndex();
    positionViewAtCell({ 0, 0 }, QQuickTableView::AlignTop | QQuickTableView::AlignLeft);
}

int QQuickTreeViewEx::depth(int row) const
{
    Q_D(const QQuickTreeViewEx);
    if (row < 0 || row >= d->m_treeModelToTableModel.rowCount())
        return -1;

    return d->m_treeModelToTableModel.depthAtRow(row);
}

bool QQuickTreeViewEx::isExpanded(int row) const
{
    Q_D(const QQuickTreeViewEx);
    if (row < 0 || row >= d->m_treeModelToTableModel.rowCount())
        return false;

    return d->m_treeModelToTableModel.isExpanded(row);
}

void QQuickTreeViewEx::expand(int row)
{
    if (row >= 0)
        expandRecursively(row, 1);
}

void QQuickTreeViewEx::expandRecursively(int row, int depth)
{
    Q_D(QQuickTreeViewEx);
    if (row >= d->m_treeModelToTableModel.rowCount())
        return;
    if (row < 0 && row != -1)
        return;
    if (depth == 0 || depth < -1)
        return;

    auto expandRowRecursively = [this, d, depth](int startRow) {
        d->m_treeModelToTableModel.expandRecursively(startRow, depth);
        // Update the expanded state of the startRow. The descendant rows that gets
        // expanded will get the correct state set from initItem/itemReused instead.
        for (int c = leftColumn(); c <= rightColumn(); ++c) {
            const QPoint treeNodeCell(c, startRow);
            if (const auto item = itemAtCell(treeNodeCell))
                d->setRequiredProperty("expanded", true, d->modelIndexAtCell(treeNodeCell), item, false);
        }
        };

    if (row >= 0) {
        // Expand only one row recursively
        const bool isExpanded = d->m_treeModelToTableModel.isExpanded(row);
        if (isExpanded && depth == 1)
            return;
        expandRowRecursively(row);
    }
    else {
        // Expand all root nodes recursively
        const auto model = d->m_treeModelToTableModel.model();
        for (int r = 0; r < model->rowCount(); ++r) {
            const int rootRow = d->m_treeModelToTableModel.itemIndex(model->index(r, 0));
            if (rootRow != -1)
                expandRowRecursively(rootRow);
        }
    }

    emit expanded(row, depth);
}

void QQuickTreeViewEx::expandToIndex(const QModelIndex& index)
{
    Q_D(QQuickTreeViewEx);

    if (!index.isValid()) {
        qmlWarning(this) << "index is not valid: " << index;
        return;
    }

    if (index.model() != d->m_treeModelToTableModel.model()) {
        qmlWarning(this) << "index doesn't belong to correct model: " << index;
        return;
    }

    if (rowAtIndex(index) != -1) {
        // index is already visible
        return;
    }

    int depth = 1;
    QModelIndex parent = index.parent();
    int row = rowAtIndex(parent);

    while (parent.isValid()) {
        if (row != -1) {
            // The node is already visible, since it maps to a row in the table!
            d->m_treeModelToTableModel.expandRow(row);

            // Update the state of the already existing delegate item
            for (int c = leftColumn(); c <= rightColumn(); ++c) {
                const QPoint treeNodeCell(c, row);
                if (const auto item = itemAtCell(treeNodeCell))
                    d->setRequiredProperty("expanded", true, d->modelIndexAtCell(treeNodeCell), item, false);
            }

            // When we hit a node that is visible, we know that all other nodes
            // up to the parent have to be visible as well, so we can stop.
            break;
        }
        else {
            d->m_treeModelToTableModel.expand(parent);
            parent = parent.parent();
            row = rowAtIndex(parent);
            depth++;
        }
    }

    emit expanded(row, depth);
}

void QQuickTreeViewEx::collapse(int row)
{
    Q_D(QQuickTreeViewEx);
    if (row < 0 || row >= d->m_treeModelToTableModel.rowCount())
        return;

    if (!d->m_treeModelToTableModel.isExpanded(row))
        return;

    d_func()->m_treeModelToTableModel.collapseRow(row);

    for (int c = leftColumn(); c <= rightColumn(); ++c) {
        const QPoint treeNodeCell(c, row);
        if (const auto item = itemAtCell(treeNodeCell))
            d->setRequiredProperty("expanded", false, d->modelIndexAtCell(treeNodeCell), item, false);
    }

    emit collapsed(row, false);
}

void QQuickTreeViewEx::collapseRecursively(int row)
{
    Q_D(QQuickTreeViewEx);
    if (row >= d->m_treeModelToTableModel.rowCount())
        return;
    if (row < 0 && row != -1)
        return;

    auto collapseRowRecursive = [this, d](int startRow) {
        // Always collapse descendants recursively,
        // even if the top row itself is already collapsed.
        d->m_treeModelToTableModel.collapseRecursively(startRow);
        // Update the expanded state of the (still visible) startRow
        for (int c = leftColumn(); c <= rightColumn(); ++c) {
            const QPoint treeNodeCell(c, startRow);
            if (const auto item = itemAtCell(treeNodeCell))
                d->setRequiredProperty("expanded", false, d->modelIndexAtCell(treeNodeCell), item, false);
        }
        };

    if (row >= 0) {
        collapseRowRecursive(row);
    }
    else {
        // Collapse all root nodes recursively
        const auto model = d->m_treeModelToTableModel.model();
        for (int r = 0; r < model->rowCount(); ++r) {
            const int rootRow = d->m_treeModelToTableModel.itemIndex(model->index(r, 0));
            if (rootRow != -1)
                collapseRowRecursive(rootRow);
        }
    }

    emit collapsed(row, true);
}

void QQuickTreeViewEx::toggleExpanded(int row)
{
    if (isExpanded(row))
        collapse(row);
    else
        expand(row);
}

void QQuickTreeViewEx::invalidateLayout()
{
    Q_D(QQuickTreeViewEx);
    d->forceLayout(false);
}

QModelIndex QQuickTreeViewEx::modelIndex(const QPoint& cell) const
{
    Q_D(const QQuickTreeViewEx);
    const QModelIndex tableIndex = d->m_treeModelToTableModel.index(cell.y(), cell.x());
    return d->m_treeModelToTableModel.mapToModel(tableIndex);
}

QPoint QQuickTreeViewEx::cellAtIndex(const QModelIndex& index) const
{
    const QModelIndex tableIndex = d_func()->m_treeModelToTableModel.mapFromModel(index);
    return QPoint(tableIndex.column(), tableIndex.row());
}

#if QT_DEPRECATED_SINCE(6, 4)
QModelIndex QQuickTreeViewEx::modelIndex(int row, int column) const
{
    static const bool compat6_4 = qEnvironmentVariable("QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4");
    if (compat6_4) {
        // XXX Qt 7: Remove this compatibility path here and in QQuickTableView.
        // In Qt 6.4.0 and 6.4.1, a source incompatible change led to row and column
        // being documented to be specified in the opposite order.
        // QT_QUICK_TABLEVIEW_COMPAT_VERSION can therefore be set to force tableview
        // to continue accepting calls to modelIndex(column, row).
        return modelIndex({ row, column });
    }
    else {
        qmlWarning(this) << "modelIndex(row, column) is deprecated. "
            "Use index(row, column) instead. For more information, see "
            "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html";
        return modelIndex({ column, row });
    }
}
#endif

void QQuickTreeViewEx::keyPressEvent(QKeyEvent* event)
{
    event->ignore();

    if (!keyNavigationEnabled())
        return;
    if (!selectionModel())
        return;

    const int row = cellAtIndex(selectionModel()->currentIndex()).y();
    switch (event->key()) {
    case Qt::Key_Left:
        collapse(row);
        event->accept();
        break;
    case Qt::Key_Right:
        expand(row);
        event->accept();
        break;
    default:
        break;
    }

    if (!event->isAccepted())
        QQuickTableView::keyPressEvent(event);
}