// The dock's app menu: its open windows, then what the .desktop file offers,
// then what the dock itself can do with the app. The shell-owned configuration
// route is deliberately last so it never displaces an app action.

import Quickshell
import Quickshell.Hyprland
import QtQuick
import qs.config
import qs.modules.bar
import qs.services
import qs.widgets

PopupWindow {
    id: root

    property Item anchorItem: null

    // The whole dock entry: { entry, windows, appId, pinned }. Narrowing this
    // to a desktop entry on the way in is what used to stop the menu offering
    // anything about the app's actual windows, or knowing whether it is pinned.
    property var app: null

    readonly property DesktopEntry entry: root.app && root.app.entry ? root.app.entry : null
    readonly property var windows: root.app && root.app.windows ? root.app.windows : []
    readonly property bool pinned: root.app ? root.app.pinned === true : false

    anchor.item: root.anchorItem
    anchor.edges: Edges.Top | Edges.Left
    anchor.gravity: Edges.Top | Edges.Right
    anchor.margins.bottom: 8

    implicitWidth: Math.min(360, Math.max(menu.implicitWidth + Theme.popoverPadding * 2, 240))
    implicitHeight: menu.implicitHeight + Theme.popoverPadding * 2
    color: "transparent"
    visible: false
    grabFocus: true

    // Long window titles are the one thing here that can be arbitrarily wide,
    // and a menu as wide as a browser tab's title is not a menu.
    function shortTitle(toplevel: var): string {
        const title = String(toplevel?.title ?? "").trim();
        if (!title)
            return root.app && root.app.appId ? root.app.appId : "Untitled window";
        return title.length > 42 ? title.slice(0, 41) + "…" : title;
    }

    function addressOf(toplevel: var): string {
        const raw = String(toplevel?.address ?? "");
        if (!raw)
            return "";
        return raw.startsWith("0x") ? raw : "0x" + raw;
    }

    function focusToplevel(toplevel: var): void {
        if (!toplevel)
            return;
        if (toplevel.workspace)
            toplevel.workspace.activate();

        const address = root.addressOf(toplevel);
        if (address)
            Hyprland.dispatch(`hl.dsp.focus({ window = "address:${address}" })`);
        else if (toplevel.wayland)
            toplevel.wayland.activate();
    }

    // Every window, not the focused one: "Quit" on a dock icon means the app,
    // which is what the icon stands for.
    function quit(): void {
        for (const toplevel of root.windows) {
            const address = root.addressOf(toplevel);
            if (address)
                Hyprland.dispatch(`hl.dsp.window.close({ window = "address:${address}" })`);
        }
    }

    // Always the whole array through DesktopPreferences, which is the only
    // thing the Settings page and the dock agree on.
    function pin(): void {
        if (!root.entry)
            return;
        const stored = Settings.dockPinned;
        if (stored.indexOf(root.entry.id) >= 0)
            return;
        DesktopPreferences.set("dockPinned", stored.concat([root.entry.id]));
    }

    function unpin(): void {
        const id = root.app && root.app.appId ? root.app.appId : "";
        if (!id)
            return;
        DesktopPreferences.set("dockPinned", Settings.dockPinned.filter(other => other !== id));
    }

    Rectangle {
        anchors.fill: parent
        radius: Theme.popoverRadius
        color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha)
        border.width: 1
        border.color: Theme.alpha(Theme.fg, 0.08)

        PrismEdge {
            anchors.top: parent.top
            anchors.topMargin: 1
            anchors.left: parent.left
            anchors.right: parent.right
            inset: parent.radius
        }

        Column {
            id: menu
            anchors.fill: parent
            anchors.margins: Theme.popoverPadding
            spacing: 2

            // ── The app's own windows ───────────────────────────────────────
            Repeater {
                id: openWindows
                model: root.windows

                delegate: TrayMenuRow {
                    required property var modelData

                    width: parent.width
                    label: root.shortTitle(modelData)
                    onActivated: {
                        root.focusToplevel(modelData);
                        root.visible = false;
                    }
                }
            }

            Rectangle {
                width: parent.width
                height: 1
                anchors.margins: 3
                visible: openWindows.count > 0
                border.width: 0
                color: Theme.alpha(Theme.fg, 0.1)
            }

            // ── What the .desktop file offers ───────────────────────────────
            Repeater {
                id: applicationActions
                model: root.entry ? root.entry.actions : []

                delegate: TrayMenuRow {
                    required property var modelData

                    width: parent.width
                    label: modelData.name
                    onActivated: {
                        modelData.execute();
                        root.visible = false;
                    }
                }
            }

            Rectangle {
                width: parent.width
                height: 1
                anchors.margins: 3
                visible: applicationActions.count > 0
                border.width: 0
                color: Theme.alpha(Theme.fg, 0.1)
            }

            // ── What the dock can do with it ────────────────────────────────
            TrayMenuRow {
                width: parent.width
                label: "New window"
                // A running application with no desktop entry cannot be
                // launched again -- there is nothing that says how.
                rowEnabled: root.entry !== null
                onActivated: {
                    if (root.entry)
                        root.entry.execute();
                    root.visible = false;
                }
            }

            TrayMenuRow {
                width: parent.width
                label: root.pinned ? "Unpin from dock" : "Pin to dock"
                // Unpinning needs only the id the pin was stored under;
                // pinning needs an entry to name, and an app whose id resolves
                // to nothing would be pinned as a hole.
                rowEnabled: root.pinned || root.entry !== null
                onActivated: {
                    if (root.pinned)
                        root.unpin();
                    else
                        root.pin();
                    root.visible = false;
                }
            }

            TrayMenuRow {
                width: parent.width
                label: root.windows.length > 1 ? "Quit all windows" : "Quit"
                rowEnabled: root.windows.length > 0
                onActivated: {
                    root.quit();
                    root.visible = false;
                }
            }

            Rectangle {
                width: parent.width
                height: 1
                anchors.margins: 3
                border.width: 0
                color: Theme.alpha(Theme.fg, 0.1)
            }

            TrayMenuRow {
                width: parent.width
                label: "Dock settings"
                onActivated: {
                    ShellState.openSettings("dock");
                    root.visible = false;
                }
            }
        }
    }
}
