import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.SystemTray
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets

BasePill {
    id: root

    enableBackgroundHover: false
    enableCursor: false

    property var parentWindow: null
    property var widgetData: null
    property string section: "right"
    property bool isAtBottom: false
    property bool isAutoHideBar: false
    property bool useOverflowPopup: !widgetData?.trayUseInlineExpansion
    readonly property var hiddenTrayIds: {
        const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || "";
        return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : [];
    }
    readonly property var allTrayItems: {
        if (!hiddenTrayIds.length) {
            return SystemTray.items.values;
        }
        return SystemTray.items.values.filter(item => {
            const itemId = item?.id || "";
            return !hiddenTrayIds.includes(itemId.toLowerCase());
        });
    }
    function getTrayItemKey(item) {
        const id = item?.id || "";
        const tooltipTitle = item?.tooltipTitle || "";
        if (!tooltipTitle || tooltipTitle === id) {
            return id;
        }
        return `${id}::${tooltipTitle}`;
    }

    function trayIconSourceFor(trayItem) {
        let icon = trayItem && trayItem.icon;
        if (typeof icon === 'string' || icon instanceof String) {
            if (icon === "")
                return "";
            if (icon.includes("?path=")) {
                const split = icon.split("?path=");
                if (split.length !== 2)
                    return icon;
                const name = split[0];
                const path = split[1];
                let fileName = name.substring(name.lastIndexOf("/") + 1);
                if (fileName.startsWith("dropboxstatus")) {
                    fileName = `hicolor/16x16/status/${fileName}`;
                }
                return `file://${path}/${fileName}`;
            }
            if (icon.startsWith("/") && !icon.startsWith("file://"))
                return `file://${icon}`;
            return icon;
        }
        return "";
    }

    function activateInlineTrayItem(trayItem, anchorItem) {
        if (!trayItem)
            return;
        if (!trayItem.onlyMenu) {
            trayItem.activate();
            return;
        }
        if (!trayItem.hasMenu)
            return;
        root.showForTrayItem(trayItem, anchorItem, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
    }

    function openInlineTrayContextMenu(trayItem, areaItem, mouse, anchorItem) {
        if (!trayItem) {
            return;
        }
        if (!trayItem.hasMenu) {
            const gp = areaItem.mapToGlobal(mouse.x, mouse.y);
            root.callContextMenuFallback(trayItem.id, Math.round(gp.x), Math.round(gp.y));
            return;
        }
        root.showForTrayItem(trayItem, anchorItem, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
    }

    function toggleIconName() {
        const edge = root.axis?.edge;
        if (root.useOverflowPopup) {
            switch (edge) {
            case "left":
                return root.menuOpen ? "keyboard_arrow_left" : "keyboard_arrow_right";
            case "right":
                return root.menuOpen ? "keyboard_arrow_right" : "keyboard_arrow_left";
            case "bottom":
                return root.menuOpen ? "keyboard_arrow_down" : "keyboard_arrow_up";
            case "top":
                return root.menuOpen ? "keyboard_arrow_up" : "keyboard_arrow_down";
            }
        }

        if (edge === "left" || edge === "right") {
            return root.menuOpen == (root.section !== "right") ? "keyboard_arrow_up" : "keyboard_arrow_down";
        }

        return root.menuOpen != (root.section === "right") ? "keyboard_arrow_left" : "keyboard_arrow_right";
    }

    // ! TODO - replace with either native dbus client (like plugins use) or just a DMS cli or something
    function callContextMenuFallback(trayItemId, globalX, globalY) {
        const script = ['ITEMS=$(dbus-send --session --print-reply --dest=org.kde.StatusNotifierWatcher /StatusNotifierWatcher org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierWatcher string:RegisteredStatusNotifierItems 2>/dev/null)', 'while IFS= read -r line; do', '  line="${line#*\\\"}"', '  line="${line%\\\"*}"', '  [ -z "$line" ] && continue', '  BUS="${line%%/*}"', '  OBJ="/${line#*/}"', '  ID=$(dbus-send --session --print-reply --dest="$BUS" "$OBJ" org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierItem string:Id 2>/dev/null | grep -oP "(?<=\\\")(.*?)(?=\\\")" | tail -1)', '  if [ "$ID" = "$1" ]; then', '    dbus-send --session --type=method_call --dest="$BUS" "$OBJ" org.kde.StatusNotifierItem.ContextMenu int32:"$2" int32:"$3"', '    exit 0', '  fi', 'done <<< "$ITEMS"',].join("\n");
        Quickshell.execDetached(["bash", "-c", script, "_", trayItemId, String(globalX), String(globalY)]);
    }

    property int _trayOrderTrigger: 0

    Connections {
        target: SessionData
        function onTrayItemOrderChanged() {
            root._trayOrderTrigger++;
        }
    }

    function sortByPreferredOrder(items, trigger) {
        void trigger;
        const savedOrder = SessionData.trayItemOrder || [];
        const orderMap = new Map();
        savedOrder.forEach((key, idx) => orderMap.set(key, idx));

        return [...items].sort((a, b) => {
            const keyA = getTrayItemKey(a);
            const keyB = getTrayItemKey(b);
            const orderA = orderMap.has(keyA) ? orderMap.get(keyA) : 10000 + items.indexOf(a);
            const orderB = orderMap.has(keyB) ? orderMap.get(keyB) : 10000 + items.indexOf(b);
            return orderA - orderB;
        });
    }

    readonly property var allSortedTrayItems: sortByPreferredOrder(allTrayItems, _trayOrderTrigger)
    readonly property var allSortedTrayItemKeys: allSortedTrayItems.map(item => getTrayItemKey(item))
    readonly property var mainBarItemsRaw: allSortedTrayItems.filter(item => !SessionData.isHiddenTrayId(root.getTrayItemKey(item)))
    readonly property var mainBarItems: mainBarItemsRaw.map((item, idx) => ({
                key: getTrayItemKey(item),
                item: item
            }))
    readonly property var hiddenBarItems: allSortedTrayItems.filter(item => SessionData.isHiddenTrayId(root.getTrayItemKey(item)))
    readonly property bool reverseInlineHorizontal: !useOverflowPopup && !isVerticalOrientation && section === "right"
    readonly property bool reverseInlineVertical: !useOverflowPopup && isVerticalOrientation && section === "right"
    readonly property var displayedMainBarItems: reverseInlineHorizontal ? [...mainBarItems].reverse() : mainBarItems
    readonly property var displayedInlineExpandedItems: (reverseInlineHorizontal ? [...hiddenBarItems].reverse() : hiddenBarItems).map(item => ({
                key: getTrayItemKey(item),
                item: item
            }))

    function moveTrayItemInFullOrder(visibleFromIndex, visibleToIndex) {
        if (visibleFromIndex === visibleToIndex || visibleFromIndex < 0 || visibleToIndex < 0)
            return;

        const fromKey = mainBarItems[visibleFromIndex]?.key ?? null;
        const toKey = mainBarItems[visibleToIndex]?.key ?? null;
        if (!fromKey || !toKey)
            return;

        const fullOrder = [...allSortedTrayItemKeys];
        const fullFromIndex = fullOrder.indexOf(fromKey);
        const fullToIndex = fullOrder.indexOf(toKey);
        if (fullFromIndex < 0 || fullToIndex < 0)
            return;

        const movedKey = fullOrder.splice(fullFromIndex, 1)[0];
        fullOrder.splice(fullToIndex, 0, movedKey);
        SessionData.setTrayItemOrder(fullOrder);
    }

    property int draggedIndex: -1
    property int dropTargetIndex: -1
    property bool suppressShiftAnimation: false
    readonly property bool hasHiddenItems: allTrayItems.length > mainBarItems.length
    readonly property bool inlineExpanded: hasHiddenItems && !useOverflowPopup && menuOpen
    visible: allTrayItems.length > 0
    opacity: allTrayItems.length > 0 ? 1 : 0

    states: [
        State {
            name: "hidden_horizontal"
            when: allTrayItems.length === 0 && !isVerticalOrientation
            PropertyChanges {
                target: root
                width: 0
            }
        },
        State {
            name: "hidden_vertical"
            when: allTrayItems.length === 0 && isVerticalOrientation
            PropertyChanges {
                target: root
                height: 0
            }
        }
    ]

    transitions: [
        Transition {
            NumberAnimation {
                properties: "width,height"
                duration: Theme.shortDuration
                easing.type: Theme.standardEasing
            }
        }
    ]

    Behavior on opacity {
        NumberAnimation {
            duration: Theme.shortDuration
            easing.type: Theme.standardEasing
        }
    }

    readonly property real trayItemSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) + 6

    readonly property real minTooltipY: {
        if (!parentScreen || !isVerticalOrientation) {
            return 0;
        }

        if (isAutoHideBar) {
            return 0;
        }

        if (parentScreen.y > 0) {
            const estimatedTopBarHeight = barThickness + barSpacing;
            return estimatedTopBarHeight;
        }

        return 0;
    }

    readonly property string autoBarShadowDirection: {
        const edge = root.axis?.edge;
        switch (edge) {
        case "top":
            return "top";
        case "bottom":
            return "bottom";
        case "left":
            return "left";
        case "right":
            return "right";
        default:
            return "bottom";
        }
    }
    readonly property string effectiveShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection

    property bool menuOpen: false
    property var currentTrayMenu: null

    content: Component {
        Item {
            implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
            implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0

            Loader {
                id: layoutLoader
                anchors.centerIn: parent
                sourceComponent: root.isVerticalOrientation ? columnComp : rowComp
            }
        }
    }

    Component {
        id: rowComp
        Row {
            spacing: 0
            layoutDirection: root.reverseInlineHorizontal ? Qt.RightToLeft : Qt.LeftToRight

            Repeater {
                model: ScriptModel {
                    values: root.displayedMainBarItems
                    objectProp: "key"
                }

                delegate: Item {
                    id: delegateRoot
                    property var trayItem: modelData.item
                    property string itemKey: modelData.key
                    property string iconSource: root.trayIconSourceFor(trayItem)

                    width: root.trayItemSize
                    height: root.barThickness
                    z: dragHandler.dragging ? 100 : 0

                    property real shiftOffset: {
                        if (root.draggedIndex < 0)
                            return 0;
                        if (index === root.draggedIndex)
                            return 0;
                        const dragIdx = root.draggedIndex;
                        const dropIdx = root.dropTargetIndex;
                        const shiftAmount = root.trayItemSize;
                        if (dropIdx < 0)
                            return 0;
                        if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx)
                            return -shiftAmount;
                        if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx)
                            return shiftAmount;
                        return 0;
                    }

                    transform: Translate {
                        x: delegateRoot.shiftOffset
                        Behavior on x {
                            enabled: !root.suppressShiftAnimation
                            NumberAnimation {
                                duration: 150
                                easing.type: Easing.OutCubic
                            }
                        }
                    }

                    Item {
                        id: dragHandler
                        anchors.fill: parent
                        property bool dragging: false
                        property point dragStartPos: Qt.point(0, 0)
                        property real dragAxisOffset: 0
                        property bool longPressing: false

                        Timer {
                            id: longPressTimer
                            interval: 400
                            repeat: false
                            onTriggered: dragHandler.longPressing = true
                        }
                    }

                    Rectangle {
                        id: visualContent
                        width: root.trayItemSize
                        height: root.trayItemSize
                        anchors.centerIn: parent
                        radius: Theme.cornerRadius
                        color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
                        border.width: dragHandler.dragging ? 2 : 0
                        border.color: Theme.primary
                        opacity: dragHandler.dragging ? 0.8 : 1.0

                        transform: Translate {
                            x: dragHandler.dragging ? dragHandler.dragAxisOffset : 0
                        }

                        IconImage {
                            id: iconImg
                            anchors.centerIn: parent
                            width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
                            height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
                            source: delegateRoot.iconSource
                            asynchronous: true
                            smooth: true
                            mipmap: true
                            visible: status === Image.Ready
                        }

                        Text {
                            anchors.centerIn: parent
                            visible: !iconImg.visible
                            text: {
                                const itemId = trayItem?.id || "";
                                if (!itemId)
                                    return "?";
                                return itemId.charAt(0).toUpperCase();
                            }
                            font.pixelSize: 10
                            color: Theme.widgetTextColor
                        }

                        DankRipple {
                            id: itemRipple
                            cornerRadius: Theme.cornerRadius
                        }
                    }

                    MouseArea {
                        id: trayItemArea
                        anchors.fill: parent
                        hoverEnabled: true
                        acceptedButtons: Qt.LeftButton | Qt.RightButton
                        cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor

                        onPressed: mouse => {
                            const pos = mapToItem(visualContent, mouse.x, mouse.y);
                            itemRipple.trigger(pos.x, pos.y);
                            if (mouse.button === Qt.LeftButton) {
                                dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
                                longPressTimer.start();
                            }
                        }

                        onReleased: mouse => {
                            longPressTimer.stop();
                            const wasDragging = dragHandler.dragging;
                            const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex;

                            if (didReorder) {
                                root.suppressShiftAnimation = true;
                                root.moveTrayItemInFullOrder(root.draggedIndex, root.dropTargetIndex);
                                Qt.callLater(() => root.suppressShiftAnimation = false);
                            }

                            dragHandler.longPressing = false;
                            dragHandler.dragging = false;
                            dragHandler.dragAxisOffset = 0;
                            root.draggedIndex = -1;
                            root.dropTargetIndex = -1;

                            if (wasDragging || mouse.button !== Qt.LeftButton)
                                return;

                            if (!delegateRoot.trayItem)
                                return;
                            if (!delegateRoot.trayItem.onlyMenu) {
                                delegateRoot.trayItem.activate();
                                return;
                            }
                            if (!delegateRoot.trayItem.hasMenu)
                                return;
                            if (root.useOverflowPopup)
                                root.menuOpen = false;
                            root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
                        }

                        onPositionChanged: mouse => {
                            if (dragHandler.longPressing && !dragHandler.dragging) {
                                const distance = Math.abs(mouse.x - dragHandler.dragStartPos.x);
                                if (distance > 5) {
                                    dragHandler.dragging = true;
                                    root.draggedIndex = root.reverseInlineHorizontal ? (root.mainBarItems.length - 1 - index) : index;
                                    root.dropTargetIndex = root.draggedIndex;
                                }
                            }
                            if (!dragHandler.dragging)
                                return;

                            const axisOffset = mouse.x - dragHandler.dragStartPos.x;
                            dragHandler.dragAxisOffset = axisOffset;
                            const itemSize = root.trayItemSize;
                            const slotOffset = Math.round(axisOffset / itemSize);
                            const visualTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
                            const newTargetIndex = root.reverseInlineHorizontal ? (root.mainBarItems.length - 1 - visualTargetIndex) : visualTargetIndex;
                            if (newTargetIndex !== root.dropTargetIndex) {
                                root.dropTargetIndex = newTargetIndex;
                            }
                        }

                        onClicked: mouse => {
                            if (dragHandler.dragging)
                                return;
                            if (mouse.button !== Qt.RightButton)
                                return;
                            if (!delegateRoot.trayItem?.hasMenu) {
                                const gp = trayItemArea.mapToGlobal(mouse.x, mouse.y);
                                root.callContextMenuFallback(delegateRoot.trayItem.id, Math.round(gp.x), Math.round(gp.y));
                                return;
                            }
                            if (root.useOverflowPopup)
                                root.menuOpen = false;
                            root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
                        }
                    }
                }
            }

            Item {
                width: root.trayItemSize
                height: root.barThickness
                visible: root.hasHiddenItems

                Rectangle {
                    id: caretButton
                    width: root.trayItemSize
                    height: root.trayItemSize
                    anchors.centerIn: parent
                    radius: Theme.cornerRadius
                    color: caretArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"

                    DankIcon {
                        anchors.centerIn: parent
                        name: root.toggleIconName()
                        size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
                        color: Theme.widgetTextColor
                    }

                    DankRipple {
                        id: caretRipple
                        cornerRadius: Theme.cornerRadius
                    }

                    MouseArea {
                        id: caretArea
                        anchors.fill: parent
                        hoverEnabled: true
                        cursorShape: Qt.PointingHandCursor
                        onPressed: mouse => {
                            caretRipple.trigger(mouse.x, mouse.y);
                        }
                        onClicked: root.menuOpen = !root.menuOpen
                    }
                }
            }

            Repeater {
                model: ScriptModel {
                    values: root.displayedInlineExpandedItems
                    objectProp: "key"
                }

                delegate: inlineExpandedTrayItemDelegate
            }
        }
    }

    Component {
        id: inlineExpandedTrayItemDelegate

        Item {
            property var trayItem: modelData.item
            property string itemKey: modelData.key
            property string iconSource: root.trayIconSourceFor(trayItem)

            width: root.isVerticalOrientation ? root.barThickness : (root.inlineExpanded ? root.trayItemSize : 0)
            height: root.isVerticalOrientation ? (root.inlineExpanded ? root.trayItemSize : 0) : root.barThickness
            visible: width > 0 || height > 0

            Behavior on width {
                enabled: !root.isVerticalOrientation
                NumberAnimation {
                    duration: Theme.shortDuration
                    easing.type: Theme.standardEasing
                }
            }

            Behavior on height {
                enabled: root.isVerticalOrientation
                NumberAnimation {
                    duration: Theme.shortDuration
                    easing.type: Theme.standardEasing
                }
            }

            Rectangle {
                id: inlineVisualContent
                width: root.trayItemSize
                height: root.trayItemSize
                x: root.isVerticalOrientation ? Math.round((parent.width - width) / 2) : (root.reverseInlineHorizontal ? parent.width - width : 0)
                y: root.isVerticalOrientation ? (root.reverseInlineVertical ? parent.height - height : 0) : Math.round((parent.height - height) / 2)
                radius: Theme.cornerRadius
                color: inlineTrayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
                opacity: root.inlineExpanded ? 1 : 0

                Behavior on opacity {
                    NumberAnimation {
                        duration: Theme.shortDuration
                        easing.type: Theme.standardEasing
                    }
                }

                IconImage {
                    id: inlineIconImg
                    anchors.centerIn: parent
                    width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
                    height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
                    source: iconSource
                    asynchronous: true
                    smooth: true
                    mipmap: true
                    visible: status === Image.Ready
                }

                Text {
                    anchors.centerIn: parent
                    visible: !inlineIconImg.visible
                    text: {
                        const itemId = trayItem?.id || "";
                        if (!itemId)
                            return "?";
                        return itemId.charAt(0).toUpperCase();
                    }
                    font.pixelSize: 10
                    color: Theme.widgetTextColor
                }

                DankRipple {
                    id: inlineItemRipple
                    cornerRadius: Theme.cornerRadius
                }
            }

            MouseArea {
                id: inlineTrayItemArea
                anchors.fill: parent
                hoverEnabled: true
                acceptedButtons: Qt.LeftButton | Qt.RightButton
                cursorShape: Qt.PointingHandCursor
                enabled: root.inlineExpanded

                onPressed: mouse => {
                    const pos = mapToItem(inlineVisualContent, mouse.x, mouse.y);
                    inlineItemRipple.trigger(pos.x, pos.y);
                }

                onClicked: mouse => {
                    if (mouse.button === Qt.LeftButton) {
                        root.activateInlineTrayItem(trayItem, inlineVisualContent);
                        return;
                    }
                    if (mouse.button !== Qt.RightButton)
                        return;
                    root.openInlineTrayContextMenu(trayItem, inlineTrayItemArea, mouse, inlineVisualContent);
                }
            }
        }
    }

    Component {
        id: verticalMainTrayItemDelegate

        Item {
            property var trayItem: modelData.item
            property string itemKey: modelData.key
            property string iconSource: root.trayIconSourceFor(trayItem)

            width: root.barThickness
            height: root.trayItemSize
            z: dragHandler.dragging ? 100 : 0

            property real shiftOffset: {
                if (root.draggedIndex < 0)
                    return 0;
                if (index === root.draggedIndex)
                    return 0;
                const dragIdx = root.draggedIndex;
                const dropIdx = root.dropTargetIndex;
                const shiftAmount = root.trayItemSize;
                if (dropIdx < 0)
                    return 0;
                if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx)
                    return -shiftAmount;
                if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx)
                    return shiftAmount;
                return 0;
            }

            transform: Translate {
                y: shiftOffset
                Behavior on y {
                    enabled: !root.suppressShiftAnimation
                    NumberAnimation {
                        duration: 150
                        easing.type: Easing.OutCubic
                    }
                }
            }

            Item {
                id: dragHandler
                anchors.fill: parent
                property bool dragging: false
                property point dragStartPos: Qt.point(0, 0)
                property real dragAxisOffset: 0
                property bool longPressing: false

                Timer {
                    id: longPressTimer
                    interval: 400
                    repeat: false
                    onTriggered: dragHandler.longPressing = true
                }
            }

            Rectangle {
                id: visualContent
                width: root.trayItemSize
                height: root.trayItemSize
                anchors.centerIn: parent
                radius: Theme.cornerRadius
                color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
                border.width: dragHandler.dragging ? 2 : 0
                border.color: Theme.primary
                opacity: dragHandler.dragging ? 0.8 : 1.0

                transform: Translate {
                    y: dragHandler.dragging ? dragHandler.dragAxisOffset : 0
                }

                IconImage {
                    id: iconImg
                    anchors.centerIn: parent
                    width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
                    height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
                    source: iconSource
                    asynchronous: true
                    smooth: true
                    mipmap: true
                    visible: status === Image.Ready
                }

                Text {
                    anchors.centerIn: parent
                    visible: !iconImg.visible
                    text: {
                        const itemId = trayItem?.id || "";
                        if (!itemId)
                            return "?";
                        return itemId.charAt(0).toUpperCase();
                    }
                    font.pixelSize: 10
                    color: Theme.widgetTextColor
                }

                DankRipple {
                    id: itemRipple
                    cornerRadius: Theme.cornerRadius
                }
            }

            MouseArea {
                id: trayItemArea
                anchors.fill: parent
                hoverEnabled: true
                acceptedButtons: Qt.LeftButton | Qt.RightButton
                cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor

                onPressed: mouse => {
                    const pos = mapToItem(visualContent, mouse.x, mouse.y);
                    itemRipple.trigger(pos.x, pos.y);
                    if (mouse.button === Qt.LeftButton) {
                        dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
                        longPressTimer.start();
                    }
                }

                onReleased: mouse => {
                    longPressTimer.stop();
                    const wasDragging = dragHandler.dragging;
                    const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex;

                    if (didReorder) {
                        root.suppressShiftAnimation = true;
                        root.moveTrayItemInFullOrder(root.draggedIndex, root.dropTargetIndex);
                        Qt.callLater(() => root.suppressShiftAnimation = false);
                    }

                    dragHandler.longPressing = false;
                    dragHandler.dragging = false;
                    dragHandler.dragAxisOffset = 0;
                    root.draggedIndex = -1;
                    root.dropTargetIndex = -1;

                    if (wasDragging || mouse.button !== Qt.LeftButton)
                        return;

                    if (!trayItem)
                        return;
                    if (!trayItem.onlyMenu) {
                        trayItem.activate();
                        return;
                    }
                    if (!trayItem.hasMenu)
                        return;
                    if (root.useOverflowPopup)
                        root.menuOpen = false;
                    root.showForTrayItem(trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
                }

                onPositionChanged: mouse => {
                    if (dragHandler.longPressing && !dragHandler.dragging) {
                        const distance = Math.abs(mouse.y - dragHandler.dragStartPos.y);
                        if (distance > 5) {
                            dragHandler.dragging = true;
                            root.draggedIndex = index;
                            root.dropTargetIndex = root.draggedIndex;
                        }
                    }
                    if (!dragHandler.dragging)
                        return;

                    const axisOffset = mouse.y - dragHandler.dragStartPos.y;
                    dragHandler.dragAxisOffset = axisOffset;
                    const itemSize = root.trayItemSize;
                    const slotOffset = Math.round(axisOffset / itemSize);
                    const newTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
                    if (newTargetIndex !== root.dropTargetIndex) {
                        root.dropTargetIndex = newTargetIndex;
                    }
                }

                onClicked: mouse => {
                    if (dragHandler.dragging)
                        return;
                    if (mouse.button !== Qt.RightButton)
                        return;
                    root.openInlineTrayContextMenu(trayItem, trayItemArea, mouse, visualContent);
                }
            }
        }
    }

    Component {
        id: columnComp
        Column {
            spacing: 0

            // Column lacks layoutDirection, so we use four repeaters with mutually exclusive models to control whether main items or expanded items appear above/ below the toggle button.
            // When reverseInlineVertical is true the first and third repeaters are empty and the second and fourth are active, and vice-versa.
            // Because items are swapped between repeaters rather than reversed within a single list, vertical drag-and-drop indices don't need remapping (unlike the horizontal RightToLeft case).
            Repeater {
                model: ScriptModel {
                    values: root.reverseInlineVertical ? [] : root.displayedMainBarItems
                    objectProp: "key"
                }
                delegate: verticalMainTrayItemDelegate
            }

            Repeater {
                model: ScriptModel {
                    values: root.reverseInlineVertical ? root.displayedInlineExpandedItems : []
                    objectProp: "key"
                }
                delegate: inlineExpandedTrayItemDelegate
            }

            Item {
                width: root.barThickness
                height: root.trayItemSize
                visible: root.hasHiddenItems

                Rectangle {
                    id: caretButtonVert
                    width: root.trayItemSize
                    height: root.trayItemSize
                    anchors.centerIn: parent
                    radius: Theme.cornerRadius
                    color: caretAreaVert.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"

                    DankIcon {
                        anchors.centerIn: parent
                        name: root.toggleIconName()
                        size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
                        color: Theme.widgetTextColor
                    }

                    DankRipple {
                        id: caretRippleVert
                        cornerRadius: Theme.cornerRadius
                    }

                    MouseArea {
                        id: caretAreaVert
                        anchors.fill: parent
                        hoverEnabled: true
                        cursorShape: Qt.PointingHandCursor
                        onPressed: mouse => {
                            caretRippleVert.trigger(mouse.x, mouse.y);
                        }
                        onClicked: root.menuOpen = !root.menuOpen
                    }
                }
            }

            Repeater {
                model: ScriptModel {
                    values: root.reverseInlineVertical ? [] : root.displayedInlineExpandedItems
                    objectProp: "key"
                }
                delegate: inlineExpandedTrayItemDelegate
            }

            Repeater {
                model: ScriptModel {
                    values: root.reverseInlineVertical ? root.displayedMainBarItems : []
                    objectProp: "key"
                }
                delegate: verticalMainTrayItemDelegate
            }
        }
    }

    PanelWindow {
        id: overflowMenu

        WindowBlur {
            targetWindow: overflowMenu
            blurX: menuContainer.x
            blurY: menuContainer.y
            blurWidth: root.menuOpen ? menuContainer.width : 0
            blurHeight: root.menuOpen ? menuContainer.height : 0
            blurRadius: Theme.cornerRadius
        }

        visible: root.useOverflowPopup && root.menuOpen
        screen: root.parentScreen
        WlrLayershell.layer: WlrLayershell.Top
        WlrLayershell.exclusiveZone: -1
        WlrLayershell.keyboardFocus: {
            if (!root.menuOpen)
                return WlrKeyboardFocus.None;
            if (CompositorService.useHyprlandFocusGrab)
                return WlrKeyboardFocus.OnDemand;
            return WlrKeyboardFocus.Exclusive;
        }
        WlrLayershell.namespace: "dms:tray-overflow-menu"
        color: "transparent"

        HyprlandFocusGrab {
            windows: [overflowMenu]
            active: CompositorService.useHyprlandFocusGrab && root.useOverflowPopup && root.menuOpen
        }

        Connections {
            target: PopoutManager
            function onPopoutOpening() {
                if (root.useOverflowPopup)
                    root.menuOpen = false;
            }
        }

        Component.onDestruction: {
            if (root.parentScreen) {
                TrayMenuManager.unregisterMenu(root.parentScreen.name);
            }
        }

        function close() {
            root.menuOpen = false;
        }

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

        readonly property real dpr: (typeof CompositorService !== "undefined" && CompositorService.getScreenScale) ? CompositorService.getScreenScale(overflowMenu.screen) : (screen?.devicePixelRatio || 1)
        property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)

        property var barBounds: {
            if (!overflowMenu.screen || !root.barConfig) {
                return {
                    "x": 0,
                    "y": 0,
                    "width": 0,
                    "height": 0,
                    "wingSize": 0
                };
            }
            const barPosition = root.axis?.edge === "left" ? 2 : (root.axis?.edge === "right" ? 3 : (root.axis?.edge === "top" ? 0 : 1));
            return SettingsData.getBarBounds(overflowMenu.screen, root.barThickness + root.barSpacing, barPosition, root.barConfig);
        }

        property real barX: barBounds.x
        property real barY: barBounds.y
        property real barWidth: barBounds.width
        property real barHeight: barBounds.height

        readonly property int barPosition: root.axis?.edge === "left" ? 2 : (root.axis?.edge === "right" ? 3 : (root.axis?.edge === "top" ? 0 : 1))
        readonly property var adjacentBarInfo: parentScreen ? SettingsData.getAdjacentBarInfo(parentScreen, barPosition, root.barConfig) : ({
                "topBar": 0,
                "bottomBar": 0,
                "leftBar": 0,
                "rightBar": 0
            })
        readonly property real effectiveBarSize: root.barThickness + root.barSpacing

        readonly property real maskX: {
            const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0;
            const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
            return Math.max(triggeringBarX, adjacentLeftBar);
        }

        readonly property real maskY: {
            const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0;
            const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
            return Math.max(triggeringBarY, adjacentTopBar);
        }

        readonly property real maskWidth: {
            const triggeringBarRight = (barPosition === 3) ? effectiveBarSize : 0;
            const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
            const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar);
            return Math.max(100, width - maskX - rightExclusion);
        }

        readonly property real maskHeight: {
            const triggeringBarBottom = (barPosition === 1) ? effectiveBarSize : 0;
            const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
            const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar);
            return Math.max(100, height - maskY - bottomExclusion);
        }

        mask: Region {
            item: Rectangle {
                x: overflowMenu.maskX
                y: overflowMenu.maskY
                width: overflowMenu.maskWidth
                height: overflowMenu.maskHeight
            }
        }

        onVisibleChanged: {
            if (visible) {
                if (currentTrayMenu) {
                    currentTrayMenu.showMenu = false;
                }
                if (root.parentScreen) {
                    TrayMenuManager.registerMenu(root.parentScreen.name, overflowMenu);
                }
                PopoutManager.closeAllPopouts();
                ModalManager.closeAllModalsExcept(null);
                updatePosition();
            } else if (!visible && root.parentScreen) {
                TrayMenuManager.unregisterMenu(root.parentScreen.name);
            }
        }

        MouseArea {
            x: overflowMenu.maskX
            y: overflowMenu.maskY
            width: overflowMenu.maskWidth
            height: overflowMenu.maskHeight
            z: -1
            enabled: root.menuOpen
            acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
            onClicked: mouse => {
                const clickX = mouse.x + overflowMenu.maskX;
                const clickY = mouse.y + overflowMenu.maskY;
                const outsideContent = clickX < menuContainer.x || clickX > menuContainer.x + menuContainer.width || clickY < menuContainer.y || clickY > menuContainer.y + menuContainer.height;

                if (!outsideContent)
                    return;
                root.menuOpen = false;
            }
        }

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

            Keys.onEscapePressed: {
                root.menuOpen = false;
            }
        }

        function updatePosition() {
            const globalPos = root.mapToGlobal(0, 0);
            const screenX = screen.x || 0;
            const screenY = screen.y || 0;
            const relativeX = globalPos.x - screenX;
            const relativeY = globalPos.y - screenY;

            if (root.isVerticalOrientation) {
                const edge = root.axis?.edge;
                let targetX = edge === "left" ? root.barThickness + root.barSpacing + Theme.popupDistance : screen.width - (root.barThickness + root.barSpacing + Theme.popupDistance);
                const adjustedY = relativeY + root.height / 2 + root.minTooltipY;
                anchorPos = Qt.point(targetX, adjustedY);
            } else {
                let targetY = root.isAtBottom ? screen.height - (root.barThickness + root.barSpacing + (barConfig?.bottomGap ?? 0) + Theme.popupDistance) : root.barThickness + root.barSpacing + (barConfig?.bottomGap ?? 0) + Theme.popupDistance;
                anchorPos = Qt.point(relativeX + root.width / 2, targetY);
            }
        }

        Item {
            id: menuContainer
            objectName: "overflowMenuContainer"

            readonly property real rawWidth: {
                const itemCount = root.hiddenBarItems.length;
                const cols = Math.min(5, itemCount);
                const itemSize = root.trayItemSize + 4;
                const spacing = 2;
                return cols * itemSize + (cols - 1) * spacing + Theme.spacingS * 2;
            }
            readonly property real rawHeight: {
                const itemCount = root.hiddenBarItems.length;
                const cols = Math.min(5, itemCount);
                const rows = Math.ceil(itemCount / cols);
                const itemSize = root.trayItemSize + 4;
                const spacing = 2;
                return rows * itemSize + (rows - 1) * spacing + Theme.spacingS * 2;
            }

            readonly property real alignedWidth: Theme.px(rawWidth, overflowMenu.dpr)
            readonly property real alignedHeight: Theme.px(rawHeight, overflowMenu.dpr)

            width: alignedWidth
            height: alignedHeight

            x: Theme.snap((() => {
                    if (root.isVerticalOrientation) {
                        const edge = root.axis?.edge;
                        if (edge === "left") {
                            const targetX = overflowMenu.anchorPos.x;
                            return Math.min(overflowMenu.screen.width - alignedWidth - 10, targetX);
                        } else {
                            const targetX = overflowMenu.anchorPos.x - alignedWidth;
                            return Math.max(10, targetX);
                        }
                    } else {
                        const left = 10;
                        const right = overflowMenu.width - alignedWidth - 10;
                        const want = overflowMenu.anchorPos.x - alignedWidth / 2;
                        return Math.max(left, Math.min(right, want));
                    }
                })(), overflowMenu.dpr)

            y: Theme.snap((() => {
                    if (root.isVerticalOrientation) {
                        const top = Math.max(overflowMenu.barY, 10);
                        const bottom = overflowMenu.height - alignedHeight - 10;
                        const want = overflowMenu.anchorPos.y - alignedHeight / 2;
                        return Math.max(top, Math.min(bottom, want));
                    } else {
                        if (root.isAtBottom) {
                            const targetY = overflowMenu.anchorPos.y - alignedHeight;
                            return Math.max(10, targetY);
                        } else {
                            const targetY = overflowMenu.anchorPos.y;
                            return Math.min(overflowMenu.screen.height - alignedHeight - 10, targetY);
                        }
                    }
                })(), overflowMenu.dpr)

            opacity: root.menuOpen ? 1 : 0
            scale: root.menuOpen ? 1 : 0.85

            Behavior on opacity {
                NumberAnimation {
                    duration: Theme.mediumDuration
                    easing.type: Theme.emphasizedEasing
                }
            }

            Behavior on scale {
                NumberAnimation {
                    duration: Theme.mediumDuration
                    easing.type: Theme.emphasizedEasing
                }
            }

            ElevationShadow {
                id: bgShadowLayer
                anchors.fill: parent
                level: Theme.elevationLevel3
                direction: root.effectiveShadowDirection
                fallbackOffset: 6
                targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
                targetRadius: Theme.cornerRadius
                sourceRect.antialiasing: true
                sourceRect.smooth: true
                shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
                layer.smooth: true
                layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
                layer.textureMirroring: ShaderEffectSource.MirrorVertically
                layer.samples: 4
            }

            Rectangle {
                anchors.fill: parent
                color: "transparent"
                radius: Theme.cornerRadius
                border.color: BlurService.borderColor
                border.width: BlurService.borderWidth
                z: 100
            }

            Grid {
                id: menuGrid
                anchors.centerIn: parent
                columns: Math.min(5, root.hiddenBarItems.length)
                spacing: 2
                rowSpacing: 2

                Repeater {
                    model: root.hiddenBarItems

                    delegate: Rectangle {
                        property var trayItem: modelData
                        property string iconSource: root.trayIconSourceFor(trayItem)

                        width: root.trayItemSize + 4
                        height: root.trayItemSize + 4
                        radius: Theme.cornerRadius
                        color: itemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0)

                        IconImage {
                            id: menuIconImg
                            anchors.centerIn: parent
                            width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
                            height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
                            source: parent.iconSource
                            asynchronous: true
                            smooth: true
                            mipmap: true
                            visible: status === Image.Ready
                        }

                        Text {
                            anchors.centerIn: parent
                            visible: !menuIconImg.visible
                            text: {
                                const itemId = trayItem?.id || "";
                                if (!itemId)
                                    return "?";
                                return itemId.charAt(0).toUpperCase();
                            }
                            font.pixelSize: 10
                            color: Theme.widgetTextColor
                        }

                        MouseArea {
                            id: itemArea
                            anchors.fill: parent
                            hoverEnabled: true
                            acceptedButtons: Qt.LeftButton | Qt.RightButton
                            cursorShape: Qt.PointingHandCursor
                            onClicked: mouse => {
                                if (!trayItem)
                                    return;
                                if (mouse.button === Qt.LeftButton && !trayItem.onlyMenu) {
                                    trayItem.activate();
                                    root.menuOpen = false;
                                    return;
                                }
                                if (!trayItem.hasMenu) {
                                    const gp = itemArea.mapToGlobal(mouse.x, mouse.y);
                                    root.callContextMenuFallback(trayItem.id, Math.round(gp.x), Math.round(gp.y));
                                    return;
                                }
                                root.showForTrayItem(trayItem, menuContainer, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
                            }
                        }
                    }
                }
            }
        }
    }

    Component {
        id: trayMenuComponent

        Rectangle {
            id: menuRoot

            property var trayItem: null
            property var anchorItem: null
            property var parentScreen: null
            property bool isAtBottom: false
            property bool isVertical: false
            property var axis: null
            property bool showMenu: false
            property var menuHandle: null

            ListModel {
                id: entryStack
            }
            function topEntry() {
                return entryStack.count ? entryStack.get(entryStack.count - 1).handle : null;
            }

            function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
                trayItem = item;
                anchorItem = anchor;
                parentScreen = screen;
                isAtBottom = atBottom;
                isVertical = vertical;
                axis = axisObj;
                menuHandle = item?.menu;

                if (parentScreen) {
                    for (var i = 0; i < Quickshell.screens.length; i++) {
                        const s = Quickshell.screens[i];
                        if (s === parentScreen) {
                            menuWindow.screen = s;
                            break;
                        }
                    }
                }

                showMenu = true;
            }

            function close() {
                showMenu = false;
            }

            Connections {
                target: menuWindow
                function onVisibleChanged() {
                    if (menuWindow.visible && parentScreen) {
                        TrayMenuManager.registerMenu(parentScreen.name, menuRoot);
                    } else if (!menuWindow.visible && parentScreen) {
                        TrayMenuManager.unregisterMenu(parentScreen.name);
                    }
                }
            }

            Component.onDestruction: {
                if (parentScreen) {
                    TrayMenuManager.unregisterMenu(parentScreen.name);
                }
            }

            Connections {
                target: PopoutManager
                function onPopoutOpening() {
                    menuRoot.close();
                }
            }

            function closeWithAction() {
                close();
            }

            function showSubMenu(entry) {
                if (!entry || !entry.hasChildren)
                    return;

                entryStack.append({
                    handle: entry
                });

                const h = entry.menu || entry;
                if (h && typeof h.updateLayout === "function")
                    h.updateLayout();

                submenuHydrator.menu = h;
                submenuHydrator.open();
                Qt.callLater(() => submenuHydrator.close());
            }

            function goBack() {
                if (!entryStack.count)
                    return;
                entryStack.remove(entryStack.count - 1);
            }

            width: 0
            height: 0
            color: "transparent"

            PanelWindow {
                id: menuWindow

                WindowBlur {
                    targetWindow: menuWindow
                    blurX: trayMenuContainer.x
                    blurY: trayMenuContainer.y
                    blurWidth: menuRoot.showMenu ? trayMenuContainer.width : 0
                    blurHeight: menuRoot.showMenu ? trayMenuContainer.height : 0
                    blurRadius: Theme.cornerRadius
                }

                WlrLayershell.namespace: "dms:tray-menu-window"
                visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false)
                WlrLayershell.layer: WlrLayershell.Top
                WlrLayershell.exclusiveZone: -1
                WlrLayershell.keyboardFocus: {
                    if (!menuRoot.showMenu)
                        return WlrKeyboardFocus.None;
                    if (CompositorService.useHyprlandFocusGrab)
                        return WlrKeyboardFocus.OnDemand;
                    return WlrKeyboardFocus.Exclusive;
                }
                color: "transparent"

                HyprlandFocusGrab {
                    windows: [menuWindow]
                    active: CompositorService.useHyprlandFocusGrab && menuRoot.showMenu
                }

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

                readonly property real dpr: (typeof CompositorService !== "undefined" && CompositorService.getScreenScale) ? CompositorService.getScreenScale(menuWindow.screen) : (screen?.devicePixelRatio || 1)
                property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)

                property var barBounds: {
                    if (!menuWindow.screen || !root.barConfig) {
                        return {
                            "x": 0,
                            "y": 0,
                            "width": 0,
                            "height": 0,
                            "wingSize": 0
                        };
                    }
                    const barPosition = root.axis?.edge === "left" ? 2 : (root.axis?.edge === "right" ? 3 : (root.axis?.edge === "top" ? 0 : 1));
                    return SettingsData.getBarBounds(menuWindow.screen, root.barThickness + root.barSpacing, barPosition, root.barConfig);
                }

                property real barX: barBounds.x
                property real barY: barBounds.y
                property real barWidth: barBounds.width
                property real barHeight: barBounds.height

                readonly property int barPosition: root.axis?.edge === "left" ? 2 : (root.axis?.edge === "right" ? 3 : (root.axis?.edge === "top" ? 0 : 1))
                readonly property var adjacentBarInfo: menuRoot.parentScreen ? SettingsData.getAdjacentBarInfo(menuRoot.parentScreen, barPosition, root.barConfig) : ({
                        "topBar": 0,
                        "bottomBar": 0,
                        "leftBar": 0,
                        "rightBar": 0
                    })
                readonly property real effectiveBarSize: root.barThickness + root.barSpacing

                readonly property real maskX: {
                    const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0;
                    const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
                    return Math.max(triggeringBarX, adjacentLeftBar);
                }

                readonly property real maskY: {
                    const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0;
                    const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
                    return Math.max(triggeringBarY, adjacentTopBar);
                }

                readonly property real maskWidth: {
                    const triggeringBarRight = (barPosition === 3) ? effectiveBarSize : 0;
                    const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
                    const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar);
                    return Math.max(100, width - maskX - rightExclusion);
                }

                readonly property real maskHeight: {
                    const triggeringBarBottom = (barPosition === 1) ? effectiveBarSize : 0;
                    const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
                    const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar);
                    return Math.max(100, height - maskY - bottomExclusion);
                }

                mask: Region {
                    item: Rectangle {
                        x: menuWindow.maskX
                        y: menuWindow.maskY
                        width: menuWindow.maskWidth
                        height: menuWindow.maskHeight
                    }
                }

                onVisibleChanged: {
                    if (visible) {
                        updatePosition();
                        if (root.useOverflowPopup)
                            root.menuOpen = false;
                        PopoutManager.closeAllPopouts();
                        ModalManager.closeAllModalsExcept(null);
                    }
                }

                MouseArea {
                    x: menuWindow.maskX
                    y: menuWindow.maskY
                    width: menuWindow.maskWidth
                    height: menuWindow.maskHeight
                    z: -1
                    enabled: menuRoot.showMenu
                    acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
                    onClicked: mouse => {
                        const clickX = mouse.x + menuWindow.maskX;
                        const clickY = mouse.y + menuWindow.maskY;
                        const outsideContent = clickX < trayMenuContainer.x || clickX > trayMenuContainer.x + trayMenuContainer.width || clickY < trayMenuContainer.y || clickY > trayMenuContainer.y + trayMenuContainer.height;

                        if (!outsideContent)
                            return;
                        menuRoot.close();
                    }
                }

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

                    Keys.onEscapePressed: {
                        if (entryStack.count > 0) {
                            menuRoot.goBack();
                        } else {
                            menuRoot.close();
                        }
                    }
                }

                function updatePosition() {
                    const targetItem = (typeof menuRoot !== "undefined" && menuRoot.anchorItem) ? menuRoot.anchorItem : root;

                    const isFromOverflowMenu = targetItem.objectName === "overflowMenuContainer";

                    if (isFromOverflowMenu) {
                        if (menuRoot.isVertical) {
                            const edge = menuRoot.axis?.edge;
                            let targetX = edge === "left" ? root.barThickness + root.barSpacing + Theme.popupDistance : screen.width - (root.barThickness + root.barSpacing + Theme.popupDistance);
                            const targetY = targetItem.y + targetItem.height / 2;
                            anchorPos = Qt.point(targetX, targetY);
                        } else {
                            const targetX = targetItem.x + targetItem.width / 2;
                            let targetY = menuRoot.isAtBottom ? screen.height - (root.barThickness + root.barSpacing + (barConfig?.bottomGap ?? 0) + Theme.popupDistance) : root.barThickness + root.barSpacing + (barConfig?.bottomGap ?? 0) + Theme.popupDistance;
                            anchorPos = Qt.point(targetX, targetY);
                        }
                    } else {
                        const globalPos = targetItem.mapToGlobal(0, 0);
                        const screenX = screen.x || 0;
                        const screenY = screen.y || 0;
                        const relativeX = globalPos.x - screenX;
                        const relativeY = globalPos.y - screenY;

                        if (menuRoot.isVertical) {
                            const edge = menuRoot.axis?.edge;
                            let targetX = edge === "left" ? root.barThickness + root.barSpacing + Theme.popupDistance : screen.width - (root.barThickness + root.barSpacing + Theme.popupDistance);
                            const adjustedY = relativeY + targetItem.height / 2 + root.minTooltipY;
                            anchorPos = Qt.point(targetX, adjustedY);
                        } else {
                            let targetY = menuRoot.isAtBottom ? screen.height - (root.barThickness + root.barSpacing + (barConfig?.bottomGap ?? 0) + Theme.popupDistance) : root.barThickness + root.barSpacing + (barConfig?.bottomGap ?? 0) + Theme.popupDistance;
                            anchorPos = Qt.point(relativeX + targetItem.width / 2, targetY);
                        }
                    }
                }

                Item {
                    id: trayMenuContainer

                    readonly property real rawWidth: Math.min(500, Math.max(250, menuColumn.implicitWidth + Theme.spacingS * 2))
                    readonly property real rawHeight: Math.max(40, menuColumn.implicitHeight + Theme.spacingS * 2)

                    readonly property real alignedWidth: Theme.px(rawWidth, menuWindow.dpr)
                    readonly property real alignedHeight: Theme.px(rawHeight, menuWindow.dpr)

                    width: alignedWidth
                    height: alignedHeight

                    x: Theme.snap((() => {
                            if (menuRoot.isVertical) {
                                const edge = menuRoot.axis?.edge;
                                if (edge === "left") {
                                    const targetX = menuWindow.anchorPos.x;
                                    return Math.min(menuWindow.screen.width - alignedWidth - 10, targetX);
                                } else {
                                    const targetX = menuWindow.anchorPos.x - alignedWidth;
                                    return Math.max(10, targetX);
                                }
                            } else {
                                const left = 10;
                                const right = menuWindow.width - alignedWidth - 10;
                                const want = menuWindow.anchorPos.x - alignedWidth / 2;
                                return Math.max(left, Math.min(right, want));
                            }
                        })(), menuWindow.dpr)

                    y: Theme.snap((() => {
                            if (menuRoot.isVertical) {
                                const top = Math.max(menuWindow.barY, 10);
                                const bottom = menuWindow.height - alignedHeight - 10;
                                const want = menuWindow.anchorPos.y - alignedHeight / 2;
                                return Math.max(top, Math.min(bottom, want));
                            } else {
                                if (menuRoot.isAtBottom) {
                                    const targetY = menuWindow.anchorPos.y - alignedHeight;
                                    return Math.max(10, targetY);
                                } else {
                                    const targetY = menuWindow.anchorPos.y;
                                    return Math.min(menuWindow.screen.height - alignedHeight - 10, targetY);
                                }
                            }
                        })(), menuWindow.dpr)

                    opacity: menuRoot.showMenu ? 1 : 0
                    scale: menuRoot.showMenu ? 1 : 0.85

                    Behavior on opacity {
                        NumberAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    Behavior on scale {
                        NumberAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    ElevationShadow {
                        id: menuBgShadowLayer
                        anchors.fill: parent
                        level: Theme.elevationLevel3
                        direction: root.effectiveShadowDirection
                        fallbackOffset: 6
                        targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
                        targetRadius: Theme.cornerRadius
                        sourceRect.antialiasing: true
                        shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
                        layer.smooth: true
                        layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
                        layer.textureMirroring: ShaderEffectSource.MirrorVertically
                    }

                    Rectangle {
                        anchors.fill: parent
                        color: "transparent"
                        radius: Theme.cornerRadius
                        border.color: BlurService.borderColor
                        border.width: BlurService.borderWidth
                        z: 100
                    }

                    QsMenuAnchor {
                        id: submenuHydrator
                        anchor.window: menuWindow
                    }

                    QsMenuOpener {
                        id: rootOpener
                        menu: menuRoot.menuHandle
                    }

                    QsMenuOpener {
                        id: subOpener
                        menu: {
                            const e = menuRoot.topEntry();
                            return e ? (e.menu || e) : null;
                        }
                    }

                    Column {
                        id: menuColumn

                        width: parent.width - Theme.spacingS * 2
                        anchors.horizontalCenter: parent.horizontalCenter
                        anchors.top: parent.top
                        anchors.topMargin: Theme.spacingS
                        spacing: 1

                        Rectangle {
                            visible: entryStack.count === 0
                            width: parent.width
                            height: 28
                            radius: Theme.cornerRadius
                            color: visibilityToggleArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0)

                            StyledText {
                                anchors.left: parent.left
                                anchors.leftMargin: Theme.spacingS
                                anchors.verticalCenter: parent.verticalCenter
                                text: menuRoot.trayItem?.id || "Unknown"
                                font.pixelSize: Theme.fontSizeSmall
                                color: Theme.surfaceTextMedium
                                elide: Text.ElideMiddle
                                width: parent.width - Theme.spacingS * 2 - 24
                            }

                            DankIcon {
                                anchors.right: parent.right
                                anchors.rightMargin: Theme.spacingS
                                anchors.verticalCenter: parent.verticalCenter
                                name: SessionData.isHiddenTrayId(root.getTrayItemKey(menuRoot.trayItem)) ? "visibility" : "visibility_off"
                                size: 16
                                color: Theme.widgetTextColor
                            }

                            MouseArea {
                                id: visibilityToggleArea
                                anchors.fill: parent
                                hoverEnabled: true
                                cursorShape: Qt.PointingHandCursor
                                onClicked: {
                                    const itemKey = root.getTrayItemKey(menuRoot.trayItem);
                                    if (!itemKey)
                                        return;
                                    if (SessionData.isHiddenTrayId(itemKey)) {
                                        SessionData.showTrayId(itemKey);
                                    } else {
                                        SessionData.hideTrayId(itemKey);
                                    }
                                    menuRoot.closeWithAction();
                                }
                            }
                        }

                        Rectangle {
                            visible: entryStack.count === 0
                            width: parent.width
                            height: 1
                            color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
                        }

                        Rectangle {
                            visible: entryStack.count > 0
                            width: parent.width
                            height: 28
                            radius: Theme.cornerRadius
                            color: backArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0)

                            Row {
                                anchors.left: parent.left
                                anchors.leftMargin: Theme.spacingS
                                anchors.verticalCenter: parent.verticalCenter
                                spacing: Theme.spacingXS

                                DankIcon {
                                    name: "arrow_back"
                                    size: 16
                                    color: Theme.widgetTextColor
                                    anchors.verticalCenter: parent.verticalCenter
                                }

                                StyledText {
                                    text: I18n.tr("Back")
                                    font.pixelSize: Theme.fontSizeSmall
                                    color: Theme.widgetTextColor
                                    anchors.verticalCenter: parent.verticalCenter
                                }
                            }

                            MouseArea {
                                id: backArea
                                anchors.fill: parent
                                hoverEnabled: true
                                cursorShape: Qt.PointingHandCursor
                                onClicked: menuRoot.goBack()
                            }
                        }

                        Rectangle {
                            visible: entryStack.count > 0
                            width: parent.width
                            height: 1
                            color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
                        }

                        Repeater {
                            model: entryStack.count ? (subOpener.children ? subOpener.children : (menuRoot.topEntry()?.children || [])) : rootOpener.children

                            Rectangle {
                                property var menuEntry: modelData

                                width: menuColumn.width
                                height: menuEntry?.isSeparator ? 1 : 28
                                radius: menuEntry?.isSeparator ? 0 : Theme.cornerRadius
                                color: {
                                    if (menuEntry?.isSeparator)
                                        return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
                                    return itemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0);
                                }

                                MouseArea {
                                    id: itemArea
                                    anchors.fill: parent
                                    hoverEnabled: true
                                    enabled: !menuEntry?.isSeparator && (menuEntry?.enabled !== false)
                                    cursorShape: Qt.PointingHandCursor

                                    onClicked: {
                                        if (!menuEntry || menuEntry.isSeparator)
                                            return;
                                        if (menuEntry.hasChildren) {
                                            menuRoot.showSubMenu(menuEntry);
                                            return;
                                        }

                                        if (typeof menuEntry.activate === "function") {
                                            menuEntry.activate();
                                        } else if (typeof menuEntry.triggered === "function") {
                                            menuEntry.triggered();
                                        }
                                        Qt.createQmlObject('import QtQuick; Timer { interval: 80; running: true; repeat: false; onTriggered: menuRoot.closeWithAction() }', menuRoot);
                                    }
                                }

                                Row {
                                    anchors.left: parent.left
                                    anchors.leftMargin: Theme.spacingS
                                    anchors.right: parent.right
                                    anchors.rightMargin: Theme.spacingS
                                    anchors.verticalCenter: parent.verticalCenter
                                    spacing: Theme.spacingXS
                                    visible: !menuEntry?.isSeparator

                                    Rectangle {
                                        width: 16
                                        height: 16
                                        anchors.verticalCenter: parent.verticalCenter
                                        visible: menuEntry?.buttonType !== undefined && menuEntry.buttonType !== 0
                                        radius: menuEntry?.buttonType === 2 ? 8 : 2
                                        border.width: 1
                                        border.color: Theme.outline
                                        color: "transparent"

                                        Rectangle {
                                            anchors.centerIn: parent
                                            width: parent.width - 6
                                            height: parent.height - 6
                                            radius: parent.radius - 3
                                            color: Theme.primary
                                            visible: menuEntry?.checkState === 2
                                        }

                                        DankIcon {
                                            anchors.centerIn: parent
                                            name: "check"
                                            size: 10
                                            color: Theme.primaryText
                                            visible: menuEntry?.buttonType === 1 && menuEntry?.checkState === 2
                                        }
                                    }

                                    Item {
                                        width: 16
                                        height: 16
                                        anchors.verticalCenter: parent.verticalCenter
                                        visible: (menuEntry?.icon ?? "") !== ""

                                        Image {
                                            anchors.fill: parent
                                            source: menuEntry?.icon || ""
                                            sourceSize.width: 16
                                            sourceSize.height: 16
                                            fillMode: Image.PreserveAspectFit
                                            smooth: true
                                        }
                                    }

                                    StyledText {
                                        text: menuEntry?.text || ""
                                        font.pixelSize: Theme.fontSizeSmall
                                        color: (menuEntry?.enabled !== false) ? Theme.surfaceText : Theme.surfaceTextMedium
                                        elide: Text.ElideRight
                                        anchors.verticalCenter: parent.verticalCenter
                                        width: Math.max(150, parent.width - 64)
                                        wrapMode: Text.NoWrap
                                    }

                                    Item {
                                        width: 16
                                        height: 16
                                        anchors.verticalCenter: parent.verticalCenter

                                        DankIcon {
                                            anchors.centerIn: parent
                                            name: "chevron_right"
                                            size: 14
                                            color: Theme.widgetTextColor
                                            visible: menuEntry?.hasChildren ?? false
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
        if (!screen)
            return;
        if (currentTrayMenu) {
            currentTrayMenu.showMenu = false;
            currentTrayMenu.destroy();
            currentTrayMenu = null;
        }

        PopoutManager.closeAllPopouts();
        ModalManager.closeAllModalsExcept(null);

        currentTrayMenu = trayMenuComponent.createObject(null);
        if (!currentTrayMenu)
            return;
        currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj);
    }
}
