/*
 *   SPDX-FileCopyrightText: 2014 Marco Martin <notmart@gmail.com>
 *   SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
 *
 *   SPDX-License-Identifier: LGPL-2.0-or-later
 */

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2

import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.mobileshell.quicksettingsplugin as QS
import org.kde.plasma.private.mobileshell as MobileShell

import 'actiondrawer/private'

Item {
    id: root

    /*
     * The intended visibility of the action drawer.
     *
     * This is separate from "visible" in order to avoid having to set
     * item visibility when it's on its own window (wasteful since the window itself can be shown/hidden).
     */
    property bool intendedToBeVisible: false

    /**
     * The model for the notification widget.
     */
    property var notificationModel

    /**
     * The model type for the notification widget.
     */
    property var notificationModelType: MobileShell.NotificationsModelType.NotificationsModel

    /**
     * The model for the quick settings.
     */
    property QS.QuickSettingsModel quickSettingsModel: QS.QuickSettingsModel {}

    /**
     * The notification settings object to be used in the notification widget.
     */
    property var notificationSettings

    /**
     * Whether actions should be subject to restricted permissions (ex. lockscreen).
     *
     * The permissionsRequested() signal emits when authentication is requested.
     */
    property bool restrictedPermissions: false

    /**
     * The amount of pixels moved by touch/mouse in the process of opening/closing the panel.
     */
    property real offset: 0

    /**
     * Same as offset value except this adds resistance when passing the open position of the current drawer state.
     */
    readonly property real offsetResistance: {
        if (!openToPinnedMode) {
            return root.calculateResistance(offset, contentContainer.maximizedQuickSettingsOffset);
        } else if (!opened) {
            return root.calculateResistance(offset, contentContainer.minimizedQuickSettingsOffset);
        } else {
            return root.calculateResistance(offset, contentContainer.maximizedQuickSettingsOffset);
        }
    }

    /**
     * Whether the panel is being dragged.
     */
    property bool dragging: false

    /**
     * Whether the panel is open after touch/mouse release from the first opening swipe.
     */
    property bool opened: false

    /**
     * Whether the panel should open to pinned mode first, with a second stroke needed to full open.
     * Only applies to portrait mode.
     */
    property bool openToPinnedMode: true

    /**
     * Direction the panel is currently moving in.
     */
    property int direction: MobileShell.Direction.None

    /**
     * The mode of the action drawer (portrait or landscape).
     */
    property int mode: (height > width && width <= largePortraitThreshold) ? MobileShell.ActionDrawer.Portrait : MobileShell.ActionDrawer.Landscape

    /**
     * At some point, even if the screen is technically portrait, if we have a ton of width it'd be best to just show the landscape mode.
     */
    readonly property real largePortraitThreshold: Kirigami.Units.gridUnit * 35

    enum Mode {
        Portrait = 0,
        Landscape
    }

    /**
     * Emitted when the drawer has closed.
     */
    signal drawerClosed()

    /**
     * Emitted when the drawer has opened.
     */
    signal drawerOpened()

    /**
     * Emitted when permissions are requested (ex. unlocking the phone).
     *
     * Only gets emitted when restrictedPermissions is set to true.
     */
    signal permissionsRequested()

    /**
     * Runs the held notification action that was pending for authentication.
     *
     * Should be called by users if authentication is successful after permissionsRequested() was emitted.
     */
    signal runPendingNotificationAction()

    property real oldOffset
    onOffsetChanged: {
        if (offset < 0) {
            offset = 0;
        }

        root.direction = (oldOffset === offset)
            ? MobileShell.Direction.None
            : (offset > oldOffset ? MobileShell.Direction.Down : MobileShell.Direction.Up);

        oldOffset = offset;

        // close panel immediately after panel is not shown, and the flickable is not being dragged
        if (opened && root.offset <= 0 && !contentContainer.swipeAreaMoving && !drawerAnimation.running) {
            root.state = "";
            offset = 0;
            focus = false;
            root.opened = false;
            root.updateState();
        }
    }

    // calculates offset resistance for the action drawer overshoots it's open position
    function calculateResistance(value : double, threshold : int) : double {
        if (value > threshold) {
            return threshold + Math.pow(value - threshold + 1, Math.max(0.8 - (value - threshold) / ((root.height - threshold) * 15), 0.35));
        } else {
            return value;
        }
    }

    function cancelAnimations() {
        root.state = "";
    }

    Keys.onEscapePressed: {
        if (intendedToBeVisible) {
            close();
        }
    }

    function open() {
        cancelAnimations();
        forceActiveFocus();
        if (openToPinnedMode) {
            root.state = "open"; // go to pinned height
        } else {
            root.state = "expand"; // go to maximized height
        }
    }

    function closeImmediately() {
        cancelAnimations();
        offset = 0;
        root.state = "close";
    }

    function close() {
        cancelAnimations();
        root.state = "close";
    }

    function expand() {
        cancelAnimations();
        root.state = "expand";
    }

    function updateState() {
        let openThreshold = Kirigami.Units.gridUnit;

        if (root.offset <= 0) {
            // close immediately, so that we don't have to wait Kirigami.Units.longDuration
            root.intendedToBeVisible = false;
            close();
        } else if (root.direction === MobileShell.Direction.None || !root.opened) {

            // if the panel has not been opened yet, run open animation only if drag passed threshold
            (root.offset < openThreshold) ? close() : open();

        } else if (root.offset > contentContainer.maximizedQuickSettingsOffset) {
            // if drag has gone past the fully expanded view
            expand();
        } else if (root.offset > contentContainer.minimizedQuickSettingsOffset) {
            // if drag is between pinned view and fully expanded view
            if (root.direction === MobileShell.Direction.Down) {
                expand();
            } else {
                // go back to pinned, or close if pinned mode is disabled
                openToPinnedMode ? open() : close();
            }
        } else if (root.direction === MobileShell.Direction.Down) {
            // if drag is between pinned view and open view, and dragging down
            open();
        } else {
            // if drag is between pinned view and open view, and dragging up
            close();
        }
    }
    Timer {
        id: updateStateTimer
        interval: 0
        onTriggered: updateState()
    }

    state: "close"

    states: [
        State {
            name: ""
            PropertyChanges {
                target: root; offset: offset
            }
        },
        State {
            name: "close"
            PropertyChanges {
                target: root; offset: 0
            }
        },
        State {
            name: "open"
            PropertyChanges {
                target: root; offset: contentContainer.minimizedQuickSettingsOffset
            }
        },
        State {
            name: "expand"
            PropertyChanges {
                target: root; offset: contentContainer.maximizedQuickSettingsOffset
            }
        }
    ]

    transitions: Transition {
        SequentialAnimation {
            MobileShell.MotionNumberAnimation {
                id: drawerAnimation
                properties: "offset"
                type: MobileShell.Motion.SpatialSlow
                duration: root.state != "" ? MobileShell.Motion.duration(type) : 0
            }
            ScriptAction {
                script: {
                    if (root.state != "") {
                        if (root.offset <= 0) {
                            root.intendedToBeVisible = false;
                            root.opened = false;
                            root.state = "";
                        } else {
                            root.opened = true;
                        }
                    }
                }
            }
        }
    }

    // action drawer ui content
    ContentContainer {
        id: contentContainer
        anchors.fill: parent

        actionDrawer: root
        quickSettingsModel: root.quickSettingsModel
    }
}
