/*
 *  SPDX-FileCopyrightText: 2023 Connor Carney <hello@connorcarney.com>
 *
 *  SPDX-License-Identifier: LGPL-2.0-or-later
 */

import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import QtQuick.Shapes as QQShapes

/**
 * @brief A pull-down to refresh indicator that can be added to any Flickable or ScrollablePage.
 */
Item {
    id: root

    //BEGIN properties
    /**
     * @brief The flickable that this indicator is attached to.
     *
     * If this is not set, the indicator will search for a Flickable in its parent chain.
     */
    property Flickable flickable: {
        let candidate = parent
        while (candidate) {
            if (candidate instanceof Flickable) {
                return candidate
            } else if (candidate instanceof Kirigami.ScrollablePage) {
                return candidate.flickable
            }
            candidate = candidate.parent
        }
        return null;
    }

    /**
     * @brief Whether to show the busy indicator at the top of the flickable
     *
     * This should be set to true whenever a refresh is in progress. It should typically
     * be set to true whe triggered() is emitted, and set to false when the refresh is
     * complete. This is not done automatically because the refresh may be triggered
     * from outside the indicator.
     */
    property bool active: false

    /**
     * @brief How far the flickable has been pulled down, between 0 (not at all) and 1 (where a refresh is triggered).
     */
    readonly property real progress: !refreshing ? Math.min(-Math.min(flickable?.verticalOvershoot ?? 0, 0) / indicatorContainer.height, 1) : 0

    /**
     * @brief Time to wait after the flickable has been pulled down before triggering a refresh
     *
     * This gives the user a chance to back out of the refresh if they release the flickable
     * before the refreshDelay has elapsed.
     */
    property int refreshDelay: 500

    /**
     * @brief emitted when the flickable is pulled down far enough to trigger a refresh
     */
    signal triggered()
    //END properties

    Item {
        id: indicatorContainer
        parent: root.flickable
        anchors {
            bottom: parent?.contentItem?.top
            bottomMargin: root.flickable.topMargin
        }

        width: flickable?.width
        height: Kirigami.Units.gridUnit * 4
        QQC2.BusyIndicator {
            id: busyIndicator
            z: 1
            anchors.centerIn: parent
            running: root.active
            visible: root.active
            // Android busywidget QQC seems to be broken at custom sizes
        }
        QQShapes.Shape {
            id: spinnerProgress
            anchors {
                fill: busyIndicator
                margins: Kirigami.Units.smallSpacing
            }
            visible: !root.active && root.progress > 0
            QQShapes.ShapePath {
                strokeWidth: Kirigami.Units.smallSpacing
                strokeColor: Kirigami.Theme.highlightColor
                fillColor: "transparent"
                PathAngleArc {
                    centerX: spinnerProgress.width / 2
                    centerY: spinnerProgress.height / 2
                    radiusX: spinnerProgress.width / 2 - Kirigami.Units.smallSpacing / 2
                    radiusY: spinnerProgress.height / 2 - Kirigami.Units.smallSpacing / 2
                    startAngle: 0
                    sweepAngle: 360 * root.progress
                }
            }
        }
    }

    onProgressChanged: {
        if (!root.active && root.progress >= 1) {
            refreshTriggerTimer.running = true;
        } else {
            refreshTriggerTimer.running = false;
        }
    }


    states: [
        State {
            name: "active"
            when: root.active
            PropertyChanges {
                target: indicatorContainer
                anchors.bottomMargin: root.flickable.topMargin - indicatorContainer.height
            }
            PropertyChanges {
                target: root.flickable
                explicit: true

                // this is not a loop because of explicit:true above
                // It adds the height of the indicator to the topMargin of the flickable
                // when we enter the active state; the change is automatically reversed
                // when returning to the base state.
                topMargin: indicatorContainer.height + root.flickable.topMargin
            }
        }
    ]

    transitions: [
        Transition {
            from: ""
            to: "active"
            enabled: root.flickable.verticalOvershoot >= 0
            reversible: true
            NumberAnimation {
                target: root.flickable
                properties: "topMargin"
                easing.type: Easing.InOutQuad
                duration: Kirigami.Units.longDuration
            }
        }
    ]

    Timer {
        id: refreshTriggerTimer
        interval: root.refreshDelay
        onTriggered: {
            if (!root.active && root.progress >= 1) {
                root.triggered()
            }
        }
    }

}
