// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

import QtQuick
import QtQuick.Controls
import HelperWidgets as HelperWidgets
import StudioControls as StudioControls
import AssetsLibraryBackend

TreeView {
    id: root
    clip: true
    interactive: verticalScrollBar.visible && !root.contextMenu.opened && !rootView.isDragging
    reuseItems: false
    boundsBehavior: Flickable.StopAtBounds
    rowSpacing: 5

    property var assetsModel: AssetsLibraryBackend.assetsModel
    property var rootView: AssetsLibraryBackend.rootView
    property var tooltipBackend: AssetsLibraryBackend.tooltipBackend

    required property Item assetsRoot
    required property StudioControls.Menu contextMenu
    property alias verticalScrollBar: verticalScrollBar

    property var selectedAssets: ({})
    // the latest file that was clicked, or changed to via Up or Down keys
    property string currentFilePath: ""

    // used to see if the op requested is to expand or to collapse.
    property int lastRowCount: -1
    // we need this to know if we need to expand further, while we're in onRowsChanged()
    property bool requestedExpandAll: true
    // used to compute the visual depth of the items we show to the user.
    property int rootPathDepth: 0
    property int rootPathRow: 0
    // i.e. first child of the root path
    readonly property int firstRow: root.rootPathRow + 1
    readonly property int lastRow: root.rows - 1
    property var __createdDirectories: []

    rowHeightProvider: (row) => {
        if (row <= root.rootPathRow)
            return 0

        return -1
    }

    ScrollBar.vertical: HelperWidgets.VerticalScrollBar {
        id: verticalScrollBar
        scrollBarVisible: root.contentHeight > root.height
    }

    model: assetsModel

    onRowsChanged: {
        if (root.rows > root.rootPathRow + 1 && !assetsModel.haveFiles ||
            root.rows <= root.rootPathRow + 1 && assetsModel.haveFiles) {
            assetsModel.syncHaveFiles()
        }

        root.updateRows()
    }

    Timer {
        id: updateRowsTimer
        interval: 200
        repeat: false

        onTriggered: {
            root.updateRows()
        }
    }

    Connections {
        target: rootView

        function onDirectoryCreated(path)
        {
            root.__createdDirectories.push(path)

            updateRowsTimer.restart()
        }

        function onDeleteSelectedAssetsRequested()
        {
            let selectedPaths = root.selectedPathsAsList()
            if (!selectedPaths.length)
                return

            let deleted = assetsModel.requestDeleteFiles(selectedPaths)
            if (!deleted) {
                confirmDeleteFiles.files = selectedPaths
                confirmDeleteFiles.open()
            }
        }
    }

    Connections {
        target: assetsModel
        function onDirectoryLoaded(path)
        {
            // updating rows for safety: the rows might have been created before the
            // directory (esp. the root path) has been loaded, so we must make sure all rows are
            // expanded -- otherwise, the tree may not become visible.

            updateRowsTimer.restart()

            let idx = assetsModel.indexForPath(path)
            let row = root.rowAtIndex(idx)
            let column = root.columnAtIndex(idx)

            if (row >= root.rootPathRow && !root.isExpanded(row))
                root.expand(row)
        }

        function onRootPathChanged()
        {
            // when we switch from one project to another, we need to reset the state of the
            // view: make sure we will do an "expand all" (otherwise, the whole tree might
            // be collapsed, and with our visible root not being the actual root of the tree,
            // the entire tree would be invisible)
            root.lastRowCount = -1
            root.requestedExpandAll = true
        }

        function onFileChanged(filePath)
        {
            rootView.invalidateThumbnail(filePath)

            let index = assetsModel.indexForPath(filePath)
            let cell = root.cellAtIndex(index)
            let fileItem = root.itemAtCell(cell)

            if (fileItem)
                fileItem.reloadImage()
        }

    } // Connections

    function addCreatedFolder(path)
    {
        root.__createdDirectories.push(path)
    }

    function selectedPathsAsList()
    {
        return Object.keys(root.selectedAssets)
            .filter(itemPath => root.selectedAssets[itemPath])
    }

    // workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721
    function resetVerticalScrollPosition()
    {
        root.contentY = 0
    }

    function updateRows()
    {
        if (root.rows <= 0)
            return

        while (root.__createdDirectories.length > 0) {
            let dirPath = root.__createdDirectories.pop()
            let index = assetsModel.indexForPath(dirPath)
            let row = root.rowAtIndex(index)

            if (row > 0)
                root.expand(row)
            else if (row === -1 && assetsModel.indexIsValid(index)) {
                // It is possible that this directory, dirPath, was created inside of a parent
                // directory that was not yet expanded in the TreeView. This can happen with the
                // bridge plugin. In such a situation, we don't have a "row" for it yet, so we have
                // to expand its parents, from root to our `index`
                let parents = assetsModel.parentIndices(index);
                parents.reverse().forEach(idx => {
                    let row = root.rowAtIndex(idx)
                    if (row > 0)
                        root.expand(row)
                })
            }
        }

        // we have no way to know beyond doubt here if updateRows() was called due
        // to a request to expand or to collapse rows - but it should be safe to
        // assume that, if we have more rows now than the last time, then it's an expand
        var expanding = (root.rows >= root.lastRowCount)

        if (expanding) {
            if (root.requestedExpandAll)
                root.__doExpandAll()
        } else {
            // on collapsing, set expandAll flag to false.
            root.requestedExpandAll = false;
        }

        root.lastRowCount = root.rows
    }

    function __doExpandAll()
    {
        let expandedAny = false
        for (let nRow = 0; nRow < root.rows; ++nRow) {
            let index = root.__modelIndex(nRow)
            if (assetsModel.isDirectory(index) && !root.isExpanded(nRow)) {
                root.expand(nRow);
                expandedAny = true
            }
        }

        if (!expandedAny)
            Qt.callLater(root.forceLayout)
    }

    function expandAll()
    {
        // In order for __doExpandAll() to be called repeatedly (every time a new node is
        // loaded, and then, expanded), we need to set requestedExpandAll to true.
        root.requestedExpandAll = true
        root.__doExpandAll()
    }

    function collapseAll()
    {
        root.resetVerticalScrollPosition()

        // collapse all, except for the root path - from the last item (leaves) up to the root
        for (let nRow = root.rows - 1; nRow >= 0; --nRow) {
            let index = root.__modelIndex(nRow)
            // we don't want to collapse the root path, because doing so will hide the contents
            // of the tree.
            if (assetsModel.filePath(index) === assetsModel.rootPath())
                break

            root.collapse(nRow)
        }
    }

    // workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721
    onContentHeightChanged: {
        if (root.contentHeight <= root.height) {
            let first = root.itemAtCell(0, root.firstRow)
            if (!first)
                root.contentY = 0
        }
    }

    function computeAllExpandedState()
    {
        var dirsWithChildren = [...Array(root.rows).keys()].filter(row => {
            let index = root.__modelIndex(row)
            return assetsModel.isDirectory(index) && assetsModel.hasChildren(index)
        })

        var countExpanded = dirsWithChildren.filter(row => root.isExpanded(row)).length

        if (countExpanded === dirsWithChildren.length)
            return "all_expanded"

        if (countExpanded === 0)
            return "all_collapsed"
        return ""
    }

    function startDropHoverOver(row)
    {
        let index = root.__modelIndex(row)
        if (assetsModel.isDirectory(index)) {
            let item = root.__getDelegateItemForIndex(index)
            if (item)
                item.isHighlighted = true
            return
        }

        let parentItem = root.__getDelegateParentForIndex(index)
        if (parentItem)
            parentItem.hasChildWithDropHover = true
    }

    function endDropHover(row)
    {
        let index = root.__modelIndex(row)
        if (assetsModel.isDirectory(index)) {
            let item = root.__getDelegateItemForIndex(index)
            if (item)
                item.isHighlighted = false
            return
        }

        let parentItem = root.__getDelegateParentForIndex(index)
        if (parentItem)
            parentItem.hasChildWithDropHover = false
    }

    function isAssetSelected(itemPath)
    {
        return root.selectedAssets[itemPath] ? true : false
    }

    function clearSelectedAssets()
    {
        root.selectedAssets = {}
    }

    function setAssetSelected(itemPath, selected)
    {
        root.selectedAssets[itemPath] = selected
        root.selectedAssetsChanged()
    }

    function __getDelegateParentForIndex(index)
    {
        let parentIndex = assetsModel.parentDirIndex(index)
        let parentCell = root.cellAtIndex(parentIndex)
        return root.itemAtCell(parentCell)
    }

    function __getDelegateItemForIndex(index)
    {
        let cell = root.cellAtIndex(index)
        return root.itemAtCell(cell)
    }

    function __modelIndex(row)
    {
        // The modelIndex() function exists since 6.3. In Qt 6.3, this modelIndex() function was a
        // member of the TreeView, while in Qt6.4 it was moved to TableView. In Qt 6.4, the order of
        // the arguments was changed, and in Qt 6.5 the order was changed again. Due to this mess,
        // the whole function was deprecated in Qt 6.4.3 and replaced with index() function.
        if (assetsRoot.qtVersion >= 0x060403)
            return root.index(row, 0)
        else if (assetsRoot.qtVersion >= 0x060400)
            return root.modelIndex(0, row)
        else
            return root.modelIndex(row, 0)
    }

    function __selectRow(row: int)
    {
        let index = root.__modelIndex(row)
        if (assetsModel.isDirectory(index))
            return

        let filePath = assetsModel.filePath(index)

        root.clearSelectedAssets()
        root.setAssetSelected(filePath, true)
        root.currentFilePath = filePath
    }

    Keys.enabled: true

    Keys.onUpPressed: {
        if (!root.currentFilePath)
            return

        let index = assetsModel.indexForPath(root.currentFilePath)
        let row = root.rowAtIndex(index)
        let nextRow = row
        let nextIndex = index

        do {
            if (nextRow <= root.firstRow)
                return // don't select hidden rows

            nextRow--
            nextIndex = root.__modelIndex(nextRow)
        } while (assetsModel.isDirectory(nextIndex))

        root.__selectRow(nextRow)
        root.positionViewAtRow(nextRow, TableView.Contain)
    }

    Keys.onDownPressed: {
        if (!root.currentFilePath)
            return

        let index = assetsModel.indexForPath(root.currentFilePath)
        let row = root.rowAtIndex(index)

        let nextRow = row
        let nextIndex = index

        do {
            if (nextRow >= root.lastRow)
                return // don't select hidden rows

            nextRow++
            nextIndex = root.__modelIndex(nextRow)
        } while (assetsModel.isDirectory(nextIndex))

        root.__selectRow(nextRow)
        root.positionViewAtRow(nextRow, TableView.Contain)
    }

    ConfirmDeleteFilesDialog {
        id: confirmDeleteFiles
        parent: root
        files: []

        onAccepted: root.clearSelectedAssets()
        onClosed: confirmDeleteFiles.files = []
    }

    DropArea {
        id: dropArea
        enabled: true
        anchors.fill: parent

        property bool __isHoveringDrop: false
        property int __rowHoveringOver: -1

        function __rowAndItem(drag)
        {
            let pos = dropArea.mapToItem(root, drag.x, drag.y)
            let cell = root.cellAtPos(pos.x, pos.y, true)
            let item = root.itemAtCell(cell)

            return [cell.y, item]
        }

        onEntered: (drag) => {
            root.assetsRoot.updateDropExtFiles(drag)

            let [row, item] = dropArea.__rowAndItem(drag)
            dropArea.__isHoveringDrop = drag.accepted && root.assetsRoot.dropSimpleExtFiles.length > 0

            if (item && dropArea.__isHoveringDrop)
                root.startDropHoverOver(row)

            dropArea.__rowHoveringOver = row
        }

        onDropped: (drag) => {
            let [row, item] = dropArea.__rowAndItem(drag)

            if (item) {
                drag.accept()
                root.endDropHover(row)

                let dirPath = item.getDirPath()

                rootView.emitExtFilesDrop(root.assetsRoot.dropSimpleExtFiles,
                                          root.assetsRoot.dropComplexExtFiles,
                                          dirPath)
            }

            dropArea.__isHoveringDrop = false
            dropArea.__rowHoveringOver = -1
        }

        onPositionChanged: (drag) => {
            let [row, item] = dropArea.__rowAndItem(drag)

            if (dropArea.__rowHoveringOver !== row && dropArea.__rowHoveringOver > -1) {
                root.endDropHover(dropArea.__rowHoveringOver)

                if (item)
                    root.startDropHoverOver(row)
            }

            dropArea.__rowHoveringOver = row
        }

        onExited: {
            if (!dropArea.__isHoveringDrop || dropArea.__rowHoveringOver === -1)
                return

            root.endDropHover(dropArea.__rowHoveringOver)

            dropArea.__isHoveringDrop = false
            dropArea.__rowHoveringOver = -1
        }
    }

    delegate: AssetDelegate {
        assetsView: root
        assetsRoot: root.assetsRoot
        indentation: 5
    }
} // TreeView
