// SPDX-License-Identifier: GPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-app.
*
* nymea-app is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-app is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-app. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

import QtQuick 2.8
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.3
import "qrc:/ui/components"
import Nymea 1.0
import NymeaApp.Utils 1.0

SettingsPageBase {
    id: root

    property ZigbeeManager zigbeeManager: null
    property ZigbeeNetwork network: null
    property ZigbeeNode node: null
    readonly property ZigbeeNode coordinatorNode: root.network.nodes.getNodeByNetworkAddress(0)

    header: NymeaHeader {
        text: qsTr("ZigBee node info")
        backButtonVisible: true
        onBackPressed: pageStack.pop()

        HeaderButton {
            imageSource: "qrc:/icons/delete.svg"
            text: qsTr("Remove node")
            onClicked: {
                var popup = removeZigbeeNodeDialogComponent.createObject(app, {zigbeeNode: root.node})
                popup.open()
            }
        }
    }

    busy: d.pendingCommandId != -1

    QtObject {
        id: d
        property int pendingCommandId: -1
        function removeNode(networkUuid, ieeeAddress) {
            d.pendingCommandId = root.zigbeeManager.removeNode(networkUuid, ieeeAddress)
        }

        onPendingCommandIdChanged: {
            print("pendingCommandId changed", pendingCommandId, wakeupDialog)
            if (pendingCommandId == -1 && wakeupDialog != null) {
                wakeupDialog.close();
                wakeupDialog.destroy();
                wakeupDialog = null
            }
        }

        property var wakeupDialog: null
    }

    Connections {
        target: root.zigbeeManager
        onCreateBindingReply: {
            print("**** create binding reply", error)
            if (commandId == d.pendingCommandId) {
                d.pendingCommandId = -1
                var props = {};
                switch (error) {
                case ZigbeeManager.ZigbeeErrorNoError:
                    return;
                case ZigbeeManager.ZigbeeErrorNetworkError:
                    props.text = qsTr("An error happened in the ZigBee network when creating the binding.");
                    break;
                case ZigbeeManager.ZigbeeErrorTimeoutError:
                    props.text = qsTr("The ZigBee device did not respond. Please try again.");
                    break;
                default:
                    props.error = error;
                }
                var comp = Qt.createComponent("/ui/components/ErrorDialog.qml")
                var popup = comp.createObject(app, props)
                popup.open();
            }
        }
        onRemoveBindingReply: {
            if (commandId == d.pendingCommandId) {
                d.pendingCommandId = -1

                var props = {};
                switch (error) {
                case ZigbeeManager.ZigbeeErrorNoError:
                    return;
                case ZigbeeManager.ZigbeeErrorNetworkError:
                    props.text = qsTr("An error happened in the ZigBee network when removing the binding.");
                    break;
                case ZigbeeManager.ZigbeeErrorTimeoutError:
                    props.text = qsTr("The ZigBee device did not respond. Please try again.");
                    break;
                default:
                    props.error = error;
                }
                var comp = Qt.createComponent("/ui/components/ErrorDialog.qml")
                var popup = comp.createObject(app, props)
                popup.open();
            }
        }

        onRemoveNodeReply: {
            if (commandId == d.pendingCommandId) {
                var props = {};
                switch (error) {
                case ZigbeeManager.ZigbeeErrorNoError:
                    pageStack.pop();
                    return;
                case ZigbeeManager.ZigbeeErrorNetworkError:
                    props.text = qsTr("An error happened in the ZigBee network when creating the binding.");
                    break;
                default:
                    props.error = error;
                    return;
                }
                var comp = Qt.createComponent("/ui/components/ErrorDialog.qml")
                var popup = comp.createObject(app, props)
                popup.open();
            }
        }
    }

    ThingsProxy {
        id: nodeThings
        engine: _engine
        paramsFilter: {"ieeeAddress": root.node.ieeeAddress}
    }

    property int signalStrength: node ? Math.round(node.lqi * 100.0 / 255.0) : 0

    NymeaItemDelegate {
        Layout.fillWidth: true
        text: root.node.model
        subText: qsTr("Model")
        prominentSubText: false
        progressive: false
    }
    NymeaItemDelegate {
        prominentSubText: false
        progressive: false
        Layout.fillWidth: true
        text: root.node.manufacturer
        subText: qsTr("Manufacturer")
    }

    NymeaItemDelegate {
        prominentSubText: false
        progressive: false
        Layout.fillWidth: true
        text: root.node.ieeeAddress
        subText: qsTr("IEEE address")
        font: Style.smallFont
    }
    NymeaItemDelegate {
        prominentSubText: false
        progressive: false
        Layout.fillWidth: true
        text: "0x" + root.node.networkAddress.toString(16)
        subText: qsTr("Network address")
    }
    NymeaItemDelegate {
        prominentSubText: false
        progressive: false
        Layout.fillWidth: true
        text: (root.node.lqi * 100 / 255).toFixed(0) + " %"
        subText: qsTr("Signal strength")
    }
    NymeaItemDelegate {
        prominentSubText: false
        progressive: false
        Layout.fillWidth: true
        text: root.node.version.length > 0 ? root.node.version : qsTr("Unknown")
        subText: qsTr("Version")
    }

    NymeaItemDelegate {
        Layout.fillWidth: true
        text: qsTr("Device endpoints")
        subText: qsTr("Show detailed information about the node")
        onClicked: pageStack.push(endpointsPageComponent)
    }

    SettingsPageSectionHeader {
        text: qsTr("Associated things")
    }

    Repeater {
        model: nodeThings
        delegate: NymeaItemDelegate {
            Layout.fillWidth: true
            property Thing thing: nodeThings.get(index)
            iconName: app.interfacesToIcon(thing.thingClass.interfaces)
            text: thing.name
            onClicked: pageStack.push("/ui/thingconfiguration/ConfigureThingPage.qml", {thing: thing})
        }

//        delegate: RowLayout {
//            id: thingDelegate
//            Layout.leftMargin: Style.margins
//            Layout.rightMargin: Style.margins
//            property Thing thing: nodeThings.get(index)
//            Layout.fillWidth: true
//            ColorIcon {
//                size: Style.iconSize
//                source: app.interfacesToIcon(thing.thingClass.interfaces)
//                color: Style.accentColor
//            }
//            TextField {
//                text: thingDelegate.thing.name
//                Layout.fillWidth: true
//                onEditingFinished: engine.thingManager.editThing(thingDelegate.thing.id, text)
//            }
//        }
    }


    ColumnLayout {
        visible: engine.jsonRpcClient.ensureServerVersion("6.2")

        SettingsPageSectionHeader {
            text: qsTr("Bindings")
        }

        Repeater {
            model: root.node.bindings
            delegate: NymeaSwipeDelegate {
                id: bindingDelegate
                Layout.fillWidth: true
                property ZigbeeNodeBinding binding: root.node.bindings[index]
                property ZigbeeNode destinationNode: root.network.nodes.getNode(binding.destinationAddress)
                property ZigbeeNodeEndpoint endpoint: root.node.getEndpoint(binding.sourceEndpointId);
                property ZigbeeCluster inputCluster: endpoint ? endpoint.getInputCluster(binding.clusterId) : null
                property ZigbeeCluster outputCluster: endpoint ? endpoint.getOutputCluster(binding.clusterId) : null
                property ZigbeeCluster usedCluster: inputCluster ? inputCluster : outputCluster
                property Thing destinationThing: destinationThings.count > 0 ? destinationThings.get(0) : null
                ThingsProxy {
                    id: destinationThings
                    engine: _engine
                    paramsFilter: {"ieeeAddress": bindingDelegate.binding.destinationAddress}
                }

                iconName: destinationNode && destinationNode == root.coordinatorNode
                          ? "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle)
                          : destinationThing
                            ? app.interfacesToIcon(destinationThing.thingClass.interfaces)
                            : "qrc:/icons/zigbee.svg"
                canDelete: true
                progressive: false
                text: {
                    if (binding.destinationAddress == "") {
                        return qsTr("Group: 0x%1").arg(NymeaUtils.pad(binding.groupAddress.toString(16), 4))
                    }
                    var ret = ""
                    if (destinationNode) {
                        if (destinationNode == root.coordinatorNode) {
                            ret += Configuration.systemName
                        } else {
                            ret += destinationNode.model
                        }
                    } else {
                        ret += binding.destinationAddress
                    }

                    if (destinationThings.count == 1) {
                        ret += " (" + destinationThings.get(0).name + ")"
                    } else if (destinationThings.count > 1) {
                        ret += " (" + destinationThings.count + " things)"
                    }

                    return ret
                }
                subText: {
                    var ret = usedCluster.clusterName();
                    if (binding.destinationAddress != "") {
                        ret += " (" + binding.sourceEndpointId + " -> " + binding.destinationEndpointId + ")"
                    }
                    return ret;
                }
                onDeleteClicked: {
                    if (!node.rxOnWhenIdle) {
                        d.wakeupDialog = wakeupDialogComponent.createObject(root)
                        d.wakeupDialog.open()
                    }
                    d.pendingCommandId = zigbeeManager.removeBinding(network.networkUuid, binding)
                }
            }
        }

        Button {
            Layout.fillWidth: true
            Layout.leftMargin: Style.margins
            Layout.rightMargin: Style.margins
            text: qsTr("Add binding")
            onClicked: {
                var dialog = addBindingComponent.createObject(root)
                dialog.open()

            }
        }
    }





    Component {
        id: removeZigbeeNodeDialogComponent

        NymeaDialog {
            id: removeZigbeeNodeDialog

            property ZigbeeNode zigbeeNode

            headerIcon: "qrc:/icons/zigbee.svg"
            title: qsTr("Remove ZigBee node") + " " + (zigbeeNode ? zigbeeNode.model : "")
            text: qsTr("Are you sure you want to remove this node from the network?")
            standardButtons: Dialog.Ok | Dialog.Cancel

            Label {
                text: qsTr("Please note that if this node has been assigned to a thing, it will also be removed from the system.")
                Layout.fillWidth: true
                wrapMode: Text.WordWrap
            }

            onAccepted: {
                d.removeNode(zigbeeNode.networkUuid, zigbeeNode.ieeeAddress)
            }
        }
    }

    Component {
        id: endpointsPageComponent
        SettingsPageBase {
            title: qsTr("Node descriptor")

            header: NymeaHeader {
                text: qsTr("ZigBee node descriptor")
                backButtonVisible: true
                onBackPressed: pageStack.pop()

                HeaderButton {
                    imageSource: "qrc:/icons/edit-copy.svg"
                    text: qsTr("Copy")
                    onClicked: {
                        PlatformHelper.toClipBoard(descriptorText.text)
                        ToolTip.show(qsTr("Copied to clipboard"), 1000);
                    }
                }
            }


            TextArea {
                id: descriptorText
                readOnly: true
                Layout.fillWidth: true
                leftPadding: Style.margins
                rightPadding: Style.margins
                topPadding: Style.margins
                bottomPadding: Style.margins
                font.family: "UbuntuMono"
                font.pixelSize: Style.smallFont.pixelSize

                text: {
                    var ret = "Manufacturer: " + root.node.manufacturer + "\n"
                    ret += "Model: " + root.node.model + "\n"
                    ret += "RxOnWhileIdle: " + root.node.rxOnWhenIdle + "\n"
                    ret += "Basic cluster version: " + root.node.version + "\n"

                    if (root.node.endpoints.length > 0) {
                        ret += "Endpoints\n";
                    }

                    for (var i = 0; i < root.node.endpoints.length; i++) {
                        var endpoint = root.node.endpoints[i]
                        var isLastEp = i == root.node.endpoints.length - 1;
                        var hasInputClusters = endpoint.inputClusters.length > 0
                        var hasOutputClusters = endpoint.outputClusters.length > 0
                        ret += (isLastEp ? "└" : "├") + (hasInputClusters || hasOutputClusters ? "┬" : "─") + " " + endpoint.endpointId + "\n"
                        ret += (isLastEp ? " " : "│") + (hasOutputClusters ? "├" : "└") + (hasInputClusters ? "┬" : "─") + " Input clusters\n"
                        for (var j = 0; j < endpoint.inputClusters.length; j++) {
                            var cluster = endpoint.inputClusters[j]
                            var isLast = j == endpoint.inputClusters.length - 1
                            ret += (isLastEp ? " " : "│") + (hasOutputClusters ? "│" : " ") + (isLast ? "└" : "├") + "─ 0x" + NymeaUtils.pad(cluster.clusterId, 4, 16) + " " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")\n"
                        }
                        if (hasOutputClusters) {
                            ret += (isLastEp ? " " : "│") + "└" + (hasOutputClusters ? "┬" : "─") +" Output clusters\n"
                            for (var j = 0; j < endpoint.outputClusters.length; j++) {
                                var cluster = endpoint.outputClusters[j]
                                var isLast = j == endpoint.outputClusters.length - 1
                                ret += (isLastEp ? " " : "│") + " " + (isLast ? "└" : "├") + "─ 0x" + NymeaUtils.pad(cluster.clusterId, 4, 16) + " " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")\n"
                            }
                        }

                    }
                    return ret;
                }
            }
        }
    }


    Component {
        id: addBindingComponent
        NymeaDialog {
            title: qsTr("Add binding")

            Label {
                text: qsTr("Source endpoint")
                Layout.fillWidth: true
            }
            ComboBox {
                Layout.fillWidth: true
                id: sourceEndpointComboBox
                model: root.node.endpoints
                textRole: "endpointId"
                displayText: currentEndpoint.endpointId
                property ZigbeeNodeEndpoint currentEndpoint: root.node.endpoints[currentIndex]
                onCurrentEndpointChanged: print("source endpoint changed", currentEndpoint.endpointId)
            }
            Label {
                text: qsTr("Target")
                Layout.fillWidth: true
            }

            RowLayout {
                // Groups not properly implemented yet. hiding it for now
                visible: false
                RadioButton {
                    id: nodeRadioButton
                    text: qsTr("Node")
                    checked: true
                    Layout.fillWidth: true
                }
                RadioButton {
                    id: groupRadioButton
                    text: qsTr("Group")
                    Layout.fillWidth: true
                }
            }

            ComboBox {
                id: destinationNodeComboBox
                Layout.fillWidth: true
                Layout.preferredHeight: Style.delegateHeight
                model: network.nodes
                visible: nodeRadioButton.checked
                property ZigbeeNode currentNode: network.nodes.get(currentIndex)
                property Thing currentNodeThing: currentNode && currentDestinationNodeThings.count > 0 ? currentDestinationNodeThings.get(0) : null
                ThingsProxy {
                    id: currentDestinationNodeThings
                    engine: _engine
                    paramsFilter: destinationNodeComboBox.currentNode ? {"ieeeAddress": destinationNodeComboBox.currentNode.ieeeAddress} : {}
                }

                contentItem: RowLayout {
                    id: destinationNodeContentItem
                    width: parent.width - destinationNodeComboBox.indicator.width - Style.smallMargins
                    height: Style.delegateHeight
                    spacing: Style.smallMargins

                    ColorIcon {
                        Layout.leftMargin: Style.smallMargins
                        size: Style.iconSize
                        name: destinationNodeComboBox.currentNode == root.coordinatorNode
                              ? "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle)
                              : destinationNodeComboBox.currentNodeThing
                                ? app.interfacesToIcon(destinationNodeComboBox.currentNodeThing.thingClass.interfaces)
                                : "qrc:/icons/zigbee.svg"
                        color: Style.accentColor
                    }

                    ColumnLayout {
                        Label {
                            Layout.fillWidth: true
                            text: destinationNodeComboBox.currentNode == root.coordinatorNode
                                  ? Configuration.systemName
                                  : destinationNodeComboBox.currentNode.model + " - " + destinationNodeComboBox.currentNode.manufacturer
                            elide: Text.ElideRight
                        }
                        Label {
                            Layout.fillWidth: true
                            text: destinationNodeComboBox.currentNode == root.coordinatorNode
                                  ? qsTr("Coordinator")
                                  : currentDestinationNodeThings.count == 1
                                    ? destinationNodeComboBox.currentNodeThing.name
                                    : currentDestinationNodeThings.count > 1
                                      ? qsTr("%1 things").arg(currentDestinationNodeThings.count)
                                      : qsTr("Unrecognized device")
                            font: Style.smallFont
                            elide: Text.ElideRight
                        }
                    }
                }
                delegate: NymeaItemDelegate {
                    id: destinationNodeDelegate
                    property ZigbeeNode node: network.nodes.get(index)
                    property Thing nodeThing: destinationNodeThings.count > 0 ? destinationNodeThings.get(0) : null
                    iconName: node == coordinatorNode
                              ? "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle)
                              : nodeThing ? app.interfacesToIcon(nodeThing.thingClass.interfaces) : "qrc:/icons/zigbee.svg"
                    width: parent.width
                    text: node == root.coordinatorNode
                          ? Configuration.systemName
                          : node.model + " - " + node.manufacturer
                    subText: node == root.coordinatorNode
                             ? qsTr("Coordinator")
                             : destinationNodeThings.count == 1
                               ? nodeThing.name
                               : destinationNodeThings.count > 1
                                 ? qsTr("%1 things").arg(destinationNodeThings.count)
                                 : qsTr("Unrecognized device")
                    progressive: false

                    ThingsProxy {
                        id: destinationNodeThings
                        engine: _engine
                        paramsFilter: {"ieeeAddress": destinationNodeDelegate.node.ieeeAddress}
                    }
                }
            }
            Label {
                visible: nodeRadioButton.checked
                text: qsTr("Destination endpoint")
                Layout.fillWidth: true
            }
            ComboBox {
                id: destinationEndpointComboBox
                visible: nodeRadioButton.checked
                Layout.fillWidth: true
                model: destinationNodeComboBox.currentNode.endpoints
                textRole: "endpointId"
                displayText: currentEndpoint.endpointId
                property ZigbeeNodeEndpoint currentEndpoint: destinationNodeComboBox.currentNode.endpoints[currentIndex]
            }

            Label {
                text: qsTr("Group address")
                visible: groupRadioButton.checked
            }

            TextField {
                id: groupAddressTextField
                visible: groupRadioButton.checked
                Layout.fillWidth: true
                text: "0"
            }

            Label {
                text: qsTr("Cluster")
                Layout.fillWidth: true
            }

            ComboBox {
                id: clusterComboBox
                Layout.fillWidth: true
                delegate: ItemDelegate {
                    width: parent.width
                    text: modelData.clusterName()
                }
                model: {
                    var ret = []
                    if (nodeRadioButton.checked) {
                        print("updating clusters", sourceEndpointComboBox.currentEndpoint, destinationNodeComboBox.currentNode, destinationEndpointComboBox.currentEndpoint)
                        if (!sourceEndpointComboBox.currentEndpoint || !destinationNodeComboBox.currentNode || !destinationEndpointComboBox.currentEndpoint) {
                            return ret;
                        }

                        if (destinationNodeComboBox.currentNode == root.coordinatorNode) {
                            for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) {
                                var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i]
                                ret.push(outputCluster)
                            }
                            for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) {
                                var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i]
                                ret.push(inputCluster)
                            }
                        } else {
                            for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) {
                                var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i]
                                print("source has cluster", outputCluster.clusterId)
                                for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.inputClusters.length; j++) {
                                    var inputCluster = destinationEndpointComboBox.currentEndpoint.inputClusters[j]
                                    print("destination has cluster", inputCluster.clusterId)
                                    if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) {
                                        ret.push(outputCluster);
                                        break;
                                    }
                                }
                            }
                            for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) {
                                var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i]
                                print("source has cluster", inputCluster.clusterId)
                                for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.outputClusters.length; j++) {
                                    var outputCluster = destinationEndpointComboBox.currentEndpoint.outputClusters[j]
                                    print("destination has cluster", outputCluster.clusterId)
                                    if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) {
                                        ret.push(inputCluster);
                                        break;
                                    }
                                }
                            }
                        }
                    } else {
                        for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) {
                            var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i]
                            ret.push(inputCluster);
                        }
                    }

                    return ret
                }
                property ZigbeeCluster currentCluster: currentValue
                displayText: currentValue.clusterName()
            }

            onAccepted: {
                if (nodeRadioButton.checked) {
                    d.pendingCommandId = root.zigbeeManager.createBinding(
                                root.network.networkUuid,
                                root.node.ieeeAddress,
                                sourceEndpointComboBox.currentEndpoint.endpointId,
                                clusterComboBox.currentCluster.clusterId,
                                destinationNodeComboBox.currentNode.ieeeAddress,
                                destinationEndpointComboBox.currentEndpoint.endpointId)
                } else {
                    d.pendingCommandId = root.zigbeeManager.createGroupBinding(
                                root.network.networkUuid,
                                root.node.ieeeAddress,
                                sourceEndpointComboBox.currentEndpoint.endpointId,
                                clusterComboBox.currentCluster.clusterId,
                                groupAddressTextField.text)
                }

                if (!root.node.rxOnWhenIdle) {
                    d.wakeupDialog = wakeupDialogComponent.createObject(root)
                    d.wakeupDialog.open()
                }
            }
        }
    }

    Component {
        id: wakeupDialogComponent
        NymeaDialog {
            id: wakeupDialog
            title: qsTr("Wake up %1").arg(root.node.model)
            text: qsTr("The selected node is a sleepy device. Please wake up the device by pressing a button.")
        }
    }
}
