// The wifi picker. This is the reason the quick settings panel exists: the
// user must never have to drop to nmcli to join a network.
//
// Everything here goes through Quickshell.Networking (NetworkManager over
// DBus) — no shelling out.

import QtQuick
import Quickshell
import Quickshell.Widgets
import Quickshell.Networking
import qs.config
import qs.widgets

Item {
    id: root

    // The WifiDevice, or null while NetworkManager is still enumerating.
    property var device: null

    // True while the section is open. Drives the NM scan request so we are not
    // burning radio time scanning for a list nobody is looking at.
    property bool active: false

    property alias maxHeight: list.maxHeight

    // SSID whose inline password field is open, and the last failure.
    property string passwordFor: ""
    property string failedSsid: ""
    property string failedText: ""

    implicitHeight: list.implicitHeight

    // GNOME's ordering: the current network, then saved ones, then by signal.
    readonly property var networks: {
        if (!root.device || !root.device.networks)
            return [];
        const list = root.device.networks.values.slice();
        list.sort((a, b) => {
            if (a.connected !== b.connected)
                return a.connected ? -1 : 1;
            if (a.known !== b.known)
                return a.known ? -1 : 1;
            return b.signalStrength - a.signalStrength;
        });
        return list;
    }

    function syncScanner(): void {
        if (root.device)
            root.device.scannerEnabled = root.active;
    }

    onActiveChanged: root.syncScanner()
    onDeviceChanged: root.syncScanner()
    Component.onCompleted: root.syncScanner()
    Component.onDestruction: {
        if (root.device)
            root.device.scannerEnabled = false;
    }

    function signalIcon(strength: real): string {
        if (strength >= 0.8)
            return "network-wireless-signal-excellent-symbolic";
        if (strength >= 0.55)
            return "network-wireless-signal-good-symbolic";
        if (strength >= 0.3)
            return "network-wireless-signal-ok-symbolic";
        if (strength > 0.05)
            return "network-wireless-signal-weak-symbolic";
        return "network-wireless-signal-none-symbolic";
    }

    // NM's failure reasons are useful but not English.
    function failureText(reason): string {
        switch (reason) {
        case ConnectionFailReason.NoSecrets:
            return "Wrong password";
        case ConnectionFailReason.WifiAuthTimeout:
            return "Authentication timed out";
        case ConnectionFailReason.WifiNetworkLost:
            return "Network out of range";
        default:
            return "Couldn't connect";
        }
    }

    function isSecured(network): bool {
        return network.security !== WifiSecurityType.Open && network.security !== WifiSecurityType.Owe && network.security !== WifiSecurityType.Unknown;
    }

    // Left click on a row. Saved and open networks connect immediately;
    // anything else reveals the password field rather than failing silently.
    function activate(network): void {
        root.failedSsid = "";
        if (network.connected)
            return;
        if (network.known || !root.isSecured(network)) {
            root.passwordFor = "";
            network.connect();
            return;
        }
        root.passwordFor = root.passwordFor === network.name ? "" : network.name;
    }

    ScrollColumn {
        id: list
        anchors.fill: parent
        maxHeight: 340

        Item {
            width: parent.width
            height: 30
            visible: root.networks.length > 0

            Text {
                anchors.left: parent.left
                anchors.leftMargin: 12
                anchors.verticalCenter: parent.verticalCenter
                text: "Available networks"
                color: Theme.fgMuted
                font.family: Theme.fontFamily
                font.pixelSize: Theme.fontSizeSmall
                font.weight: Font.DemiBold
            }
        }

        Text {
            width: parent.width
            visible: root.networks.length === 0
            topPadding: 12
            bottomPadding: 12
            horizontalAlignment: Text.AlignHCenter
            text: root.device ? (Networking.wifiEnabled ? "Scanning…" : "Wi-Fi is off") : "No Wi-Fi adapter"
            color: Theme.fgMuted
            font.family: Theme.fontFamily
            font.pixelSize: Theme.fontSize
        }

        Repeater {
            model: root.networks

            Column {
                id: entry

                required property var modelData

                width: parent.width
                spacing: 0

                readonly property bool secured: root.isSecured(entry.modelData)
                readonly property string ssid: entry.modelData.name

                // Reported by NM when a connection attempt gives up. Bad
                // passwords land here, so re-open the field to retry.
                Connections {
                    target: entry.modelData
                    function onConnectionFailed(reason) {
                        root.failedSsid = entry.ssid;
                        root.failedText = root.failureText(reason);
                        if (entry.secured && !entry.modelData.known)
                            root.passwordFor = entry.ssid;
                    }
                }

                RowButton {
                    width: parent.width
                    icon: root.signalIcon(entry.modelData.signalStrength)
                    iconFallback: "network-wireless-symbolic"
                    label: entry.ssid !== "" ? entry.ssid : "Hidden network"
                    selected: entry.modelData.connected
                    sublabel: {
                        if (entry.modelData.stateChanging)
                            return "Connecting…";
                        if (entry.modelData.connected)
                            return "Connected";
                        if (root.failedSsid === entry.ssid)
                            return root.failedText;
                        if (entry.modelData.known)
                            return "Saved";
                        return "";
                    }
                    onClicked: root.activate(entry.modelData)

                    ThemedIcon {
                        anchors.verticalCenter: parent.verticalCenter
                        size: 14
                        visible: entry.secured
                        tint: Theme.fgMuted
                        icon: "network-wireless-encrypted-symbolic"
                        iconFallback: "changes-prevent-symbolic"
                    }

                    IconButton {
                        visible: entry.modelData.connected
                        size: 26
                        iconSize: 13
                        icon: "window-close-symbolic"
                        onClicked: entry.modelData.disconnect()
                    }
                }

                Section {
                    id: pskSection
                    width: parent.width
                    expanded: root.passwordFor === entry.ssid

                    onExpandedChanged: {
                        if (pskSection.expanded)
                            pskInput.forceActiveFocus();
                        else
                            pskInput.text = "";
                    }

                    Rectangle {
                        anchors.left: parent.left
                        anchors.right: parent.right
                        anchors.leftMargin: 10
                        anchors.rightMargin: 10
                        height: 42
                        y: 4
                        radius: Theme.cardRadius - 2
                        border.width: 1
                        border.color: Theme.alpha(Theme.accent, 0.4)
                        color: Theme.alpha(Theme.fg, 0.08)

                        TextInput {
                            id: pskInput
                            anchors.left: parent.left
                            anchors.leftMargin: 12
                            anchors.right: sendButton.left
                            anchors.rightMargin: 6
                            anchors.verticalCenter: parent.verticalCenter
                            echoMode: TextInput.Password
                            color: Theme.fg
                            selectionColor: Theme.alpha(Theme.accent, 0.5)
                            selectedTextColor: Theme.fg
                            font.family: Theme.fontFamily
                            font.pixelSize: Theme.fontSize
                            clip: true

                            onAccepted: {
                                if (pskInput.text.length > 0) {
                                    root.failedSsid = "";
                                    entry.modelData.connectWithPsk(pskInput.text);
                                    root.passwordFor = "";
                                }
                            }

                            Text {
                                anchors.fill: parent
                                verticalAlignment: Text.AlignVCenter
                                visible: pskInput.text === ""
                                text: "Password"
                                color: Theme.fgMuted
                                font: pskInput.font
                            }
                        }

                        IconButton {
                            id: sendButton
                            anchors.right: parent.right
                            anchors.rightMargin: 4
                            anchors.verticalCenter: parent.verticalCenter
                            size: 30
                            iconSize: 15
                            icon: "go-next-symbolic"
                            onClicked: pskInput.accepted()
                        }
                    }
                }
            }
        }
    }
}
