// Replaces GNOME's power-off dialog. Opened by Ctrl+Alt+Delete and from the
// quick-settings footer.
//
// Log Out, Restart and Power Off arm on the first press and fire on the second
// (see PowerButton); Lock and Suspend are one press because neither loses work.

import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import QtQuick
import qs.config
import qs.services

PanelWindow {
    id: win

    visible: ShellState.powerMenuOpen
    color: "transparent"

    anchors {
        top: true
        bottom: true
        left: true
        right: true
    }

    exclusiveZone: 0

    WlrLayershell.namespace: "qs-popover-power"
    WlrLayershell.layer: WlrLayer.Overlay
    WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive

    property int currentIndex: 0

    // Hibernate appears only where it can deliver: logind answers "yes" only
    // with a resume-capable swap configured, and a menu entry that fails
    // silently is worse than none. Checked once per shell run -- swap does
    // not come and go.
    property bool canHibernate: false

    Process {
        id: hibernateProbe
        command: ["busctl", "call", "org.freedesktop.login1",
            "/org/freedesktop/login1", "org.freedesktop.login1.Manager",
            "CanHibernate"]
        running: true
        stdout: StdioCollector {
            onStreamFinished: win.canHibernate = this.text.includes('"yes"')
        }
    }

    // A uwsm-managed session logs out by stopping the uwsm unit; a plain
    // "Hyprland" session must ask the compositor to exit instead. The branch
    // used to test merely that the uwsm BINARY existed -- but uwsm is always
    // installed here, so in a plain session `uwsm stop` found no unit to
    // stop, failed, and the button did nothing. Nor is `uwsm check is-active`
    // the test: it only checks graphical-session.target, which the plain
    // session also reaches. What only the managed session has is uwsm's own
    // compositor unit, wayland-wm@*.service, so that is what decides.
    readonly property string logoutScript: 'if systemctl --user list-units --no-legend --state=active "wayland-wm@*.service" 2>/dev/null | grep -q .; then exec uwsm stop; else exec hyprctl dispatch "hl.dsp.exit()"; fi'

    // The rows actually shown: an entry may declare `available: false` to
    // withdraw itself (hibernate on a machine with no resume swap).
    readonly property var entries: allEntries.filter(entry => entry.available !== false)

    readonly property var allEntries: [
        {
            glyph: "󰌾",
            label: "Lock",
            destructive: false,
            cmd: ["loginctl", "lock-session"]
        },
        {
            glyph: "󰗼",
            label: "Log Out",
            destructive: true,
            cmd: ["sh", "-c", win.logoutScript]
        },
        {
            glyph: "󰒲",
            label: "Suspend",
            destructive: false,
            cmd: ["systemctl", "suspend"]
        },
        {
            glyph: "󰋊",
            label: "Hibernate",
            destructive: false,
            available: win.canHibernate,
            cmd: ["systemctl", "hibernate"]
        },
        {
            glyph: "󰜉",
            label: "Restart",
            destructive: true,
            cmd: ["systemctl", "reboot"]
        },
        {
            glyph: "󰐥",
            label: "Power Off",
            destructive: true,
            cmd: ["systemctl", "poweroff"]
        }
    ]

    onVisibleChanged: {
        if (!win.visible)
            return;
        // Never reopen with a destructive button still armed from last time.
        win.currentIndex = 0;
        for (let i = 0; i < rep.count; i++)
            rep.itemAt(i).disarm();
        keys.forceActiveFocus();
    }

    function run(index: int): void {
        ShellState.close();
        Quickshell.execDetached(win.entries[index].cmd);
    }

    function move(step: int): void {
        // Moving away disarms, so Enter can never fire a button the user only
        // meant to walk past.
        rep.itemAt(win.currentIndex).disarm();
        win.currentIndex = (win.currentIndex + step + win.entries.length) % win.entries.length;
    }

    FocusScope {
        id: keys
        anchors.fill: parent
        focus: true

        Keys.onPressed: event => {
            switch (event.key) {
            case Qt.Key_Escape:
                ShellState.close();
                break;
            case Qt.Key_Left:
                win.move(-1);
                break;
            case Qt.Key_Right:
                win.move(1);
                break;
            case Qt.Key_Return:
            case Qt.Key_Enter:
            case Qt.Key_Space:
                rep.itemAt(win.currentIndex).trigger();
                break;
            default:
                return;
            }
            event.accepted = true;
        }

        Rectangle {
            anchors.fill: parent
            color: Theme.alpha(Theme.bgDark, Theme.overlayAlpha)
            border.width: 0

            MouseArea {
                anchors.fill: parent
                onClicked: ShellState.close()
            }
        }

        Rectangle {
            anchors.centerIn: parent
            implicitWidth: row.implicitWidth + 2 * 24
            implicitHeight: row.implicitHeight + 2 * 24
            radius: Theme.popoverRadius + 8
            border.width: 0
            color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha)

            // Swallow clicks that land on the panel but miss a tile, so the
            // outside-click handler underneath doesn't close the menu.
            MouseArea {
                anchors.fill: parent
            }

            Row {
                id: row
                anchors.centerIn: parent
                spacing: 14

                Repeater {
                    id: rep
                    model: win.entries

                    PowerButton {
                        required property var modelData
                        required property int index

                        glyph: modelData.glyph
                        label: modelData.label
                        destructive: modelData.destructive
                        selected: index === win.currentIndex
                        onActivated: win.run(index)
                    }
                }
            }
        }
    }
}
