// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later

import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Effects

import org.kde.plasma.components 3.0 as PC3
import org.kde.plasma.private.mobileshell.state as MobileShellState
import org.kde.plasma.private.mobileshell as MobileShell
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
import org.kde.kirigami as Kirigami

import "./delegate"
import "./private"

Item {
    id: root
    property Folio.HomeScreen folio
    property MobileShell.MaskManager maskManager

    property int pageNum

    property var pageModel
    property var homeScreen

    onActiveFocusChanged: {
        if (activeFocus) {
            // Focus on first delegate when this page is focused
            let firstDelegate = findFirstDelegate();
            if (!firstDelegate) {
                return;
            }
            firstDelegate.keyboardFocus();
        }
    }

    // Returns the first delegate on the page, or null if none exist
    function findFirstDelegate() {
        let firstDelegate = delegateRepeater.itemAt(0);
        if (!firstDelegate) {
            return null;
        }

        for (let i = 0; i < delegateRepeater.count; ++i) {
            let delegate = delegateRepeater.itemAt(i);

            const isAppOrFolder = delegate.pageDelegate.type === Folio.FolioDelegate.Application || delegate.pageDelegate.type === Folio.FolioDelegate.Folder;

            // If it's on an earlier row, or on the same row but earlier column
            if (isAppOrFolder
                && (delegate.row < firstDelegate.row
                    || (delegate.column < firstDelegate.column
                        && delegate.row === firstDelegate.row))) {
                firstDelegate = delegate;
            }
        }
        return firstDelegate;
    }

    // Returns the next application/folder delegate on the page from the given delegate
    // in a certain direction, or null if none exist.
    function findNextAppDelegate(delegate, direction: MobileShell.Direction) {
        let dx = 0;
        let dy = 0;
        switch (direction) {
        case MobileShell.Direction.Up:
            dy = -1;
            break;
        case MobileShell.Direction.Down:
            dy = 1;
            break;
        case MobileShell.Direction.Left:
            dx = -1;
            break;
        case MobileShell.Direction.Right:
            dx = 1;
            break;
        }

        let x = delegate.column + dx;
        let y = delegate.row + dy;

        // Loop in direction to find delegate
        while (x >= 0 && x < folio.HomeScreenState.pageColumns && y >= 0 && y < folio.HomeScreenState.pageRows) {
            for (let i = 0; i < delegateRepeater.count; ++i) {
                let delegate = delegateRepeater.itemAt(i);

                if (delegate.row === y && delegate.column === x
                    && (delegate.pageDelegate.type === Folio.FolioDelegate.Application
                        || delegate.pageDelegate.type === Folio.FolioDelegate.Folder)) {
                    return delegate;
                }
            }
            x += dx;
            y += dy;
        }
        return null;
    }

    MobileShell.HapticsEffect {
        id: haptics
    }

    // background when in settings view (for rearranging pages)
    Rectangle {
        id: settingsViewBackground
        anchors.fill: parent
        color: Qt.rgba(255, 255, 255, 0.2)
        opacity: folio.HomeScreenState.settingsOpenProgress
        radius: Kirigami.Units.largeSpacing
    }

    // square that shows when hovering over a spot to drop a delegate on
    PlaceholderDelegate {
        id: dragDropFeedback
        folio: root.folio
        width: folio.HomeScreenState.pageCellWidth
        height: folio.HomeScreenState.pageCellHeight

        property var dropPosition: folio.HomeScreenState.dragState.candidateDropPosition
        property var dropDelegate: folio.HomeScreenState.dragState.dropDelegate
        property bool dropDelegateIsWidget: dropDelegate && dropDelegate.type === Folio.FolioDelegate.Widget

        // only show if it is an empty spot on this page
        visible: folio.HomeScreenState.isDraggingDelegate &&
            dropPosition.location === Folio.DelegateDragPosition.Pages &&
            dropPosition.page === root.pageNum &&
            !dropDelegateIsWidget &&
            folio.HomeScreenState.getPageDelegateAt(root.pageNum, dropPosition.pageRow, dropPosition.pageColumn) === null

        x: dropPosition.pageColumn * folio.HomeScreenState.pageCellWidth
        y: dropPosition.pageRow * folio.HomeScreenState.pageCellHeight
    }

    // square that shows when a widget hovers over a spot to drop a delegate on
    Rectangle {
        id: widgetDragDropFeedback
        width: (dropDelegateIsWidget ? dropDelegate.widget.gridWidth : 0) * folio.HomeScreenState.pageCellWidth
        height: (dropDelegateIsWidget ? dropDelegate.widget.gridHeight : 0) * folio.HomeScreenState.pageCellHeight

        property var dropPosition: folio.HomeScreenState.dragState.candidateDropPosition
        property var dropDelegate: folio.HomeScreenState.dragState.dropDelegate
        property bool dropDelegateIsWidget: dropDelegate && dropDelegate.type === Folio.FolioDelegate.Widget

        // only show if the widget can be placed here
        visible: folio.HomeScreenState.isDraggingDelegate &&
            dropPosition.location === Folio.DelegateDragPosition.Pages &&
            dropPosition.page === root.pageNum &&
            dropDelegateIsWidget &&
            pageModel.canAddDelegate(dropPosition.pageRow, dropPosition.pageColumn, dropDelegate)

        radius: Kirigami.Units.cornerRadius
        color: Qt.rgba(255, 255, 255, 0.3)

        x: dropPosition.pageColumn * folio.HomeScreenState.pageCellWidth
        y: dropPosition.pageRow * folio.HomeScreenState.pageCellHeight

        layer.enabled: true
        layer.effect: DelegateShadow {}
    }

    // repeater of all delegates in the page
    Repeater {
        id: delegateRepeater
        model: root.pageModel

        delegate: Item {
            id: delegate

            property Folio.FolioPageDelegate pageDelegate: model.delegate
            property int row: pageDelegate.row
            property int column: pageDelegate.column

            property var dragState: folio.HomeScreenState.dragState

            property bool isDropPositionThis: dragState.candidateDropPosition.location === Folio.DelegateDragPosition.Pages &&
                dragState.candidateDropPosition.page === root.pageNum &&
                dragState.candidateDropPosition.pageRow === delegate.pageDelegate.row &&
                dragState.candidateDropPosition.pageColumn === delegate.pageDelegate.column

            property bool isAppHoveredOver: folio.HomeScreenState.isDraggingDelegate &&
                dragState.dropDelegate &&
                dragState.dropDelegate.type === Folio.FolioDelegate.Application &&
                isDropPositionThis

            implicitWidth: loader.item ? loader.item.implicitWidth : 0
            implicitHeight: loader.item ? loader.item.implicitHeight : 0
            width: loader.item ? loader.item.width : 0
            height: loader.item ? loader.item.height : 0

            x: column * folio.HomeScreenState.pageCellWidth
            y: row * folio.HomeScreenState.pageCellHeight

            visible: row >= 0 && row < folio.HomeScreenState.pageRows &&
            column >= 0 && column < folio.HomeScreenState.pageColumns

            // called when we want to delete this delegate
            function removeSelf() {
                // remove from model
                root.pageModel.removeDelegate(delegate.row, delegate.column);

                // TODO: this doesn't work, because the removeDelegate calls removes this entire object and the calls fail
                // // delete empty pages at the end, and snap position to page that exists
                // folio.PageListModel.deleteEmptyPagesAtEnd();
                // folio.HomeScreenState.snapPage();
            }

            // Keyboard navigation between delegates
            Keys.onPressed: (event) => {
                let direction = MobileShell.Direction.Up;
                switch (event.key) {
                case Qt.Key_Up:
                    direction = MobileShell.Direction.Up;
                    break;
                case Qt.Key_Down:
                    direction = MobileShell.Direction.Down;
                    break;
                case Qt.Key_Left:
                    direction = MobileShell.Direction.Left;
                    break;
                case Qt.Key_Right:
                    direction = MobileShell.Direction.Right;
                    break;
                default:
                    return;
                }

                let nextDelegate = root.findNextAppDelegate(delegate, direction);
                if (nextDelegate) {
                    nextDelegate.keyboardFocus();
                    event.accepted = true;
                }
            }

            function keyboardFocus() {
                if (loader.item) {
                    loader.item.keyboardFocus();
                }
            }

            Loader {
                id: loader
                anchors.top: parent.top
                anchors.left: parent.left

                sourceComponent: {
                    if (delegate.pageDelegate.type === Folio.FolioDelegate.Application) {
                        return appComponent;
                    } else if (delegate.pageDelegate.type === Folio.FolioDelegate.Folder) {
                        return folderComponent;
                    } else if (delegate.pageDelegate.type === Folio.FolioDelegate.Widget) {
                        return widgetComponent;
                    } else {
                        return noneComponent;
                    }
                }
            }

            Component {
                id: noneComponent

                Item {}
            }

            Component {
                id: appComponent

                AppDelegate {
                    id: appDelegate
                    folio: root.folio
                    maskManager: root.maskManager
                    name: folio.FolioSettings.showPagesAppLabels ? delegate.pageDelegate.application.name : ""
                    application: delegate.pageDelegate.application
                    turnToFolder: delegate.isAppHoveredOver
                    turnToFolderAnimEnabled: folio.HomeScreenState.isDraggingDelegate

                    implicitWidth: folio.HomeScreenState.pageCellWidth
                    implicitHeight: folio.HomeScreenState.pageCellHeight
                    width: folio.HomeScreenState.pageCellWidth
                    height: folio.HomeScreenState.pageCellHeight

                    // do not show if the drop animation is running to this delegate
                    visible: !(root.homeScreen.dropAnimationRunning && delegate.isDropPositionThis)

                    // don't show label in drag and drop mode
                    labelOpacity: delegate.opacity

                    onPressAndHold: {
                        // prevent editing if lock layout is enabled
                        if (folio.FolioSettings.lockLayout) return;

                        let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.pageDelegate, appDelegate.delegateItem);
                        folio.HomeScreenState.startDelegatePageDrag(
                            mappedCoords.x,
                            mappedCoords.y,
                            appDelegate.pressPosition.x,
                            appDelegate.pressPosition.y,
                            root.pageNum,
                            delegate.pageDelegate.row,
                            delegate.pageDelegate.column
                        );

                        contextMenu.open();
                        haptics.buttonVibrate();
                    }
                    onPressAndHoldReleased: {
                        // cancel the event if the delegate is not dragged
                        if (folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) {
                            homeScreen.cancelDelegateDrag();
                        }
                    }

                    onRightMousePress: {
                        contextMenu.open();
                    }

                    ContextMenuLoader {
                        id: contextMenu

                        // close menu when drag starts
                        Connections {
                            target: folio.HomeScreenState

                            function onDelegateDragStarted() {
                                contextMenu.close();
                            }
                        }

                        actions: [
                            Kirigami.Action {
                                icon.name: "emblem-favorite"
                                text: i18n("Remove")
                                enabled: !folio.FolioSettings.lockLayout
                                onTriggered: delegate.removeSelf()
                            }
                        ]
                    }
                }
            }

            Component {
                id: folderComponent

                AppFolderDelegate {
                    id: appFolderDelegate
                    folio: root.folio
                    maskManager: root.maskManager
                    name: folio.FolioSettings.showPagesAppLabels ? delegate.pageDelegate.folder.name : ""
                    folder: delegate.pageDelegate.folder

                    implicitWidth: folio.HomeScreenState.pageCellWidth
                    implicitHeight: folio.HomeScreenState.pageCellHeight
                    width: folio.HomeScreenState.pageCellWidth
                    height: folio.HomeScreenState.pageCellHeight

                    // do not show if the drop animation is running to this delegate, and the drop delegate is a folder
                    visible: !(root.homeScreen.dropAnimationRunning &&
                        delegate.isDropPositionThis &&
                        delegate.dragState.dropDelegate.type === Folio.FolioDelegate.Folder)

                    // don't show label in drag and drop mode
                    labelOpacity: delegate.opacity

                    appHoveredOver: delegate.isAppHoveredOver

                    onAfterClickAnimation: {
                        const pos = homeScreen.prepareFolderOpen(appFolderDelegate.contentItem);
                        folio.HomeScreenState.openFolder(pos.x, pos.y, folder);
                    }

                    onPressAndHold: {
                        // prevent editing if lock layout is enabled
                        if (folio.FolioSettings.lockLayout) return;

                        let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.pageDelegate, appFolderDelegate.delegateItem);
                        folio.HomeScreenState.startDelegatePageDrag(
                            mappedCoords.x,
                            mappedCoords.y,
                            appFolderDelegate.pressPosition.x,
                            appFolderDelegate.pressPosition.y,
                            root.pageNum,
                            delegate.pageDelegate.row,
                            delegate.pageDelegate.column
                        );

                        contextMenu.open();
                        haptics.buttonVibrate();
                    }

                    onPressAndHoldReleased: {
                        // cancel the event if the delegate is not dragged
                        if (folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) {
                            homeScreen.cancelDelegateDrag();
                        }
                    }

                    onRightMousePress: {
                        contextMenu.open();
                    }

                    ContextMenuLoader {
                        id: contextMenu

                        // close menu when drag starts
                        Connections {
                            target: folio.HomeScreenState

                            function onDelegateDragStarted() {
                                contextMenu.close();
                            }
                        }

                        actions: [
                            Kirigami.Action {
                                icon.name: "emblem-favorite"
                                text: i18n("Remove")
                                enabled: !folio.FolioSettings.lockLayout
                                onTriggered: deleteDialog.open()
                            }
                        ]

                        ConfirmDeleteFolderDialogLoader {
                            id: deleteDialog
                            parent: root.homeScreen
                            onAccepted: delegate.removeSelf()
                        }
                    }
                }
            }

            Component {
                id: widgetComponent

                WidgetDelegate {
                    id: widgetDelegate
                    folio: root.folio

                    // don't reparent applet if the drop animation is running to this delegate
                    // background: there is only one "visual" instance of the widget, once this delegate loads
                    //             it will reparent it to here (but we don't want it to happen while the drop animation is running)
                    property bool suppressAppletReparent: (root.homeScreen.currentlyDraggedWidget === delegate.pageDelegate.widget)
                    && delegate.isDropPositionThis

                    visible: !suppressAppletReparent
                    widget: suppressAppletReparent ? null : delegate.pageDelegate.widget

                    onStartEditMode: (pressPoint) => {
                        // prevent editing if lock layout is enabled
                        if (folio.FolioSettings.lockLayout) return;

                        let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.pageDelegate, widgetDelegate);
                        folio.HomeScreenState.startDelegatePageDrag(
                            mappedCoords.x,
                            mappedCoords.y,
                            pressPoint.x - mappedCoords.x,
                            pressPoint.y - mappedCoords.y,
                            root.pageNum,
                            delegate.pageDelegate.row,
                            delegate.pageDelegate.column
                        );

                        widgetConfig.startOpen();
                        haptics.buttonVibrate();
                    }

                    onPressReleased: {
                        // cancel the event if the delegate is not dragged
                        if (folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) {
                            folio.HomeScreenState.cancelDelegateDrag();
                            widgetConfig.fullyOpen();
                        }
                    }

                    layer.enabled: widgetDelegate.editMode && folio.FolioSettings.lockLayout === false
                    layer.effect: DarkenEffect {}

                    PC3.ToolTip {
                        visible: widgetDelegate.editMode && pressed
                        text: i18n("Release to configure, drag to move")
                    }

                    WidgetDelegateConfig {
                        id: widgetConfig
                        folio: root.folio
                        homeScreen: root.homeScreen

                        pageModel: root.pageModel
                        pageDelegate: delegate.pageDelegate
                        widget: delegate.pageDelegate.widget

                        pageNum: root.pageNum
                        row: delegate.row
                        column: delegate.column

                        widgetWidth: widgetDelegate.widgetWidth
                        widgetHeight: widgetDelegate.widgetHeight
                        widgetX: delegate.x + root.anchors.leftMargin + root.homeScreen.leftMargin
                        widgetY: delegate.y + root.anchors.topMargin + root.homeScreen.topMargin

                        topWidgetBackgroundPadding: widgetDelegate.topWidgetBackgroundPadding
                        bottomWidgetBackgroundPadding: widgetDelegate.bottomWidgetBackgroundPadding
                        leftWidgetBackgroundPadding: widgetDelegate.leftWidgetBackgroundPadding
                        rightWidgetBackgroundPadding: widgetDelegate.rightWidgetBackgroundPadding

                        anchors.fill: parent

                        onRemoveRequested: {
                            if (widget.applet) {
                                widget.destroyApplet();
                            }
                            delegate.removeSelf();
                        }

                        onClosed: widgetDelegate.editMode = false
                    }
                }
            }
        }
    }
}
