import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell.Services.Mpris
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets

Item {
    id: root

    LayoutMirroring.enabled: I18n.isRtl
    LayoutMirroring.childrenInherit: true

    property MprisPlayer activePlayer: MprisController.activePlayer
    readonly property real stableLength: MprisController.activePlayerStableLength
    property var allPlayers: MprisController.availablePlayers
    property var targetScreen: null
    property real popoutX: 0
    property real popoutY: 0
    property real popoutWidth: 0
    property real popoutHeight: 0
    property real contentOffsetY: 0
    property string section: ""
    property int barPosition: SettingsData.Position.Top

    readonly property color accent: MediaAccentService.accent
    readonly property color onAccent: MediaAccentService.onAccent
    readonly property color accentHover: MediaAccentService.accentHover
    readonly property color accentPressed: MediaAccentService.accentPressed

    signal showVolumeDropdown(point pos, var screen, bool rightEdge, var player, var players)
    signal showAudioDevicesDropdown(point pos, var screen, bool rightEdge)
    signal showPlayersDropdown(point pos, var screen, bool rightEdge, var player, var players)
    signal hideDropdowns
    signal dropdownButtonExited
    signal dropdownButtonEntered

    property bool volumeExpanded: false
    property bool devicesExpanded: false
    property bool playersExpanded: false

    function resetDropdownStates() {
        volumeExpanded = false;
        devicesExpanded = false;
        playersExpanded = false;
    }

    readonly property bool isRightEdge: {
        if (barPosition === SettingsData.Position.Right)
            return true;
        if (barPosition === SettingsData.Position.Left)
            return false;
        return section === "right";
    }
    readonly property bool __isChromeBrowser: {
        if (!activePlayer?.identity)
            return false;
        const id = activePlayer.identity.toLowerCase();
        return id.includes("chrome") || id.includes("chromium");
    }
    readonly property bool volumeAvailable: !!((activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio))
    readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
    readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)

    property bool isSwitching: false

    // Derived "no players" state: always correct, no timers.
    readonly property int _playerCount: allPlayers ? allPlayers.length : 0
    readonly property bool _noneAvailable: _playerCount === 0
    readonly property bool showNoPlayerNow: (!_switchHold) && (_noneAvailable || !activePlayer)

    property bool _switchHold: false
    Timer {
        id: _switchHoldTimer
        interval: 1500
        repeat: false
        onTriggered: _switchHold = false
    }

    onActivePlayerChanged: {
        if (!activePlayer) {
            isSwitching = false;
            _switchHold = true;
            _switchHoldTimer.restart();
            return;
        }
        isSwitching = true;
        _switchHold = true;
        _switchHoldTimer.restart();
    }

    function maybeFinishSwitch() {
        if (activePlayer && activePlayer.trackTitle !== "") {
            isSwitching = false;
            _switchHold = false;
        }
    }

    readonly property real ratio: {
        if (!activePlayer || stableLength <= 0) {
            return 0;
        }
        const pos = (activePlayer.position || 0) % Math.max(1, stableLength);
        const calculatedRatio = pos / stableLength;
        return Math.max(0, Math.min(1, calculatedRatio));
    }

    implicitWidth: SettingsData.showWeekNumber ? 736 : 700
    implicitHeight: playerContent.height + playerContent.anchors.topMargin * 2

    Connections {
        target: activePlayer
        ignoreUnknownSignals: true
        function onTrackTitleChanged() {
            _switchHoldTimer.restart();
            maybeFinishSwitch();
        }
    }

    Connections {
        target: MprisController
        function onAvailablePlayersChanged() {
            if ((MprisController.availablePlayers?.length || 0) === 0)
                isSwitching = false;
            _switchHold = true;
            _switchHoldTimer.restart();
        }
    }

    function getAudioDeviceIcon(device) {
        if (!device || !device.name)
            return "speaker";

        const name = device.name.toLowerCase();

        if (name.includes("bluez") || name.includes("bluetooth"))
            return "headset";
        if (name.includes("hdmi"))
            return "tv";
        if (name.includes("usb"))
            return "headset";
        if (name.includes("analog") || name.includes("built-in"))
            return "speaker";

        return "speaker";
    }

    function getVolumeIcon() {
        if (!volumeAvailable)
            return "volume_off";

        const volume = currentVolume;

        if (usePlayerVolume) {
            if (volume === 0.0)
                return "music_off";
            return "music_note";
        }

        if (volume === 0.0)
            return "volume_off";
        if (volume <= 0.33)
            return "volume_down";
        if (volume <= 0.66)
            return "volume_up";
        return "volume_up";
    }

    function adjustVolume(step) {
        if (!volumeAvailable)
            return;
        const maxVol = usePlayerVolume ? 100 : AudioService.sinkMaxVolume;
        const current = Math.round(currentVolume * 100);
        const newVolume = Math.min(maxVol, Math.max(0, current + step));

        SessionData.suppressOSDTemporarily();
        if (usePlayerVolume) {
            activePlayer.volume = newVolume / 100;
        } else if (AudioService.sink?.audio) {
            AudioService.sink.audio.volume = newVolume / 100;
        }
    }

    function triggerVolumeDropdown() {
        if (!volumeAvailable)
            return;
        if (volumeExpanded)
            return;
        hideDropdowns();
        volumeExpanded = true;
        const buttonsOnRight = !isRightEdge;
        const btnY = volumeButton.y + volumeButton.height / 2;
        const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
        const screenY = popoutY + contentOffsetY + btnY;
        showVolumeDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
    }

    function toggleMute() {
        if (!volumeAvailable)
            return;
        SessionData.suppressOSDTemporarily();
        if (currentVolume > 0) {
            volumeButton.previousVolume = currentVolume;
            if (usePlayerVolume) {
                activePlayer.volume = 0;
            } else if (AudioService.sink?.audio) {
                AudioService.sink.audio.volume = 0;
            }
        } else {
            const restoreVolume = volumeButton.previousVolume > 0 ? volumeButton.previousVolume : 0.5;
            if (usePlayerVolume) {
                activePlayer.volume = restoreVolume;
            } else if (AudioService.sink?.audio) {
                AudioService.sink.audio.volume = restoreVolume;
            }
        }
    }

    function handleKeyEvent(event) {
        if (!activePlayer)
            return false;

        // 1. Number keys 0-9 to seek to 0%-90%
        if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) {
            if (activePlayer.canSeek && stableLength > 0) {
                const ratio = (event.key - Qt.Key_0) * 0.1;
                const targetPosition = ratio * stableLength;
                activePlayer.position = Math.max(0.1, Math.min(targetPosition, stableLength * 0.99));
                return true;
            }
        }

        // 2. Left / Right arrows to seek backward / forward 5s
        if (event.key === Qt.Key_Left) {
            if (activePlayer.canSeek) {
                activePlayer.position = Math.max(0.1, activePlayer.position - 5);
                return true;
            }
        }
        if (event.key === Qt.Key_Right) {
            if (activePlayer.canSeek && stableLength > 0) {
                activePlayer.position = Math.max(0.1, Math.min(stableLength - 1, activePlayer.position + 5));
                return true;
            }
        }

        // 3. Up / Down arrows to adjust volume
        if (event.key === Qt.Key_Up) {
            adjustVolume(5);
            triggerVolumeDropdown();
            dropdownButtonExited();
            return true;
        }
        if (event.key === Qt.Key_Down) {
            adjustVolume(-5);
            triggerVolumeDropdown();
            dropdownButtonExited();
            return true;
        }

        // 4. Spacebar to play/pause
        if (event.key === Qt.Key_Space) {
            if (activePlayer.canTogglePlaying) {
                activePlayer.togglePlaying();
                return true;
            }
        }

        // 5. M key to toggle mute
        if (event.key === Qt.Key_M) {
            toggleMute();
            triggerVolumeDropdown();
            dropdownButtonExited();
            return true;
        }

        return false;
    }

    property bool isSeeking: false

    Timer {
        interval: 1000
        running: activePlayer?.playbackState === MprisPlaybackState.Playing && !isSeeking
        repeat: true
        onTriggered: activePlayer?.positionChanged()
    }

    Item {
        id: bgContainer
        anchors.fill: parent

        // Fall back to the live mpris url so the background is never blank.
        readonly property string curArt: {
            const resolved = TrackArtService.resolvedArtUrl;
            if (resolved !== "")
                return resolved;
            const p = root.activePlayer;
            if (!p)
                return "";
            if (p.trackArtUrl)
                return p.trackArtUrl;
            const m = p.metadata;
            return m && m["mpris:artUrl"] ? m["mpris:artUrl"].toString() : "";
        }
        // Two layers crossfade: new art loads into the hidden one and fades in once decoded.
        property bool _showA: true
        visible: layerA.ready || layerB.ready

        onCurArtChanged: syncArt()
        Component.onCompleted: syncArt()

        function syncArt() {
            if (curArt === "")
                return;
            const front = _showA ? layerA : layerB;
            const back = _showA ? layerB : layerA;
            if (front.art == curArt)
                return;
            if (back.art == curArt) {
                // Already decoded in the hidden layer; flip once ready (else promote() does).
                if (back.ready)
                    _showA = !_showA;
                return;
            }
            back.art = curArt;
        }

        // Flip to the hidden layer only when it holds the current art, ignoring stale
        // Ready re-emits (e.g. popout re-expose) that would otherwise ping-pong _showA.
        function promote(layer) {
            const back = _showA ? layerB : layerA;
            if (layer !== back)
                return;
            if (layer.art != curArt)
                return;
            _showA = (layer === layerA);
            root.maybeFinishSwitch();
        }

        BgBlurLayer {
            id: layerA
            front: bgContainer._showA
            onLoaded: bgContainer.promote(layerA)
        }

        BgBlurLayer {
            id: layerB
            front: !bgContainer._showA
            onLoaded: bgContainer.promote(layerB)
        }

        Rectangle {
            anchors.fill: parent
            radius: Theme.cornerRadius
            color: Theme.surface
            opacity: 0.3
        }
    }

    component BgBlurLayer: ClippingRectangle {
        id: layer
        property alias art: layerImg.source
        readonly property bool ready: layerImg.status === Image.Ready && layerImg.source != ""
        property bool front: false
        signal loaded

        anchors.fill: parent
        radius: Theme.cornerRadius
        color: "transparent"
        antialiasing: true
        opacity: front ? 0.7 : 0

        Behavior on opacity {
            NumberAnimation {
                duration: 350
                easing.type: Easing.InOutQuad
            }
        }

        Image {
            id: layerImg
            anchors.centerIn: parent
            width: Math.max(parent.width, parent.height) * 1.1
            height: width
            fillMode: Image.PreserveAspectCrop
            asynchronous: true
            cache: true
            visible: false
            onStatusChanged: {
                if (status === Image.Ready && source != "")
                    layer.loaded();
            }
        }

        MultiEffect {
            anchors.centerIn: parent
            width: layerImg.width
            height: layerImg.height
            source: layerImg
            blurEnabled: true
            blurMax: 64
            blur: 0.8
            saturation: -0.2
            brightness: -0.25
        }
    }

    Column {
        anchors.centerIn: parent
        spacing: Theme.spacingM
        visible: showNoPlayerNow

        DankIcon {
            name: "music_note"
            size: Theme.iconSize * 3
            color: Theme.surfaceTextSecondary
            anchors.horizontalCenter: parent.horizontalCenter
        }

        StyledText {
            text: I18n.tr("No Active Players")
            font.pixelSize: Theme.fontSizeLarge
            color: Theme.surfaceTextMedium
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }

    Item {
        anchors.fill: parent
        clip: false
        visible: !_noneAvailable && (!showNoPlayerNow)
        ColumnLayout {
            id: playerContent
            width: 484
            height: 370
            spacing: Theme.spacingXS
            anchors.top: parent.top
            anchors.topMargin: 20
            anchors.horizontalCenter: parent.horizontalCenter

            Item {
                width: parent.width
                height: 200
                clip: false

                DankAlbumArt {
                    width: Math.min(parent.width * 0.8, parent.height * 0.9)
                    height: width
                    anchors.centerIn: parent
                    activePlayer: root.activePlayer
                    artUrl: TrackArtService.resolvedArtUrl
                    accentColor: MediaAccentService.accent
                    cavaService: CavaService
                }
            }

            // Song Info and Controls Section
            Item {
                width: parent.width
                Layout.fillHeight: true

                Column {
                    id: songInfo
                    width: parent.width
                    spacing: Theme.spacingXS
                    anchors.top: parent.top
                    anchors.horizontalCenter: parent.horizontalCenter

                    StyledText {
                        text: MprisController.stableTitle || I18n.tr("Unknown Track")
                        font.pixelSize: Theme.fontSizeLarge
                        font.weight: Font.Bold
                        color: Theme.surfaceText
                        width: parent.width
                        horizontalAlignment: Text.AlignHCenter
                        elide: Text.ElideRight
                        wrapMode: Text.WordWrap
                        maximumLineCount: 2
                    }

                    StyledText {
                        text: MprisController.stableArtist || I18n.tr("Unknown Artist")
                        font.pixelSize: Theme.fontSizeMedium
                        color: Theme.surfaceTextMedium
                        width: parent.width
                        horizontalAlignment: Text.AlignHCenter
                        elide: Text.ElideRight
                        wrapMode: Text.WordWrap
                        maximumLineCount: 1
                    }

                    StyledText {
                        text: activePlayer?.trackAlbum || ""
                        font.pixelSize: Theme.fontSizeSmall
                        color: Theme.surfaceTextSecondary
                        width: parent.width
                        horizontalAlignment: Text.AlignHCenter
                        elide: Text.ElideRight
                        wrapMode: Text.WordWrap
                        maximumLineCount: 1
                        // Reserve the line so late album metadata doesn't shift the seekbar.
                        height: Math.max(implicitHeight, Theme.fontSizeSmall * 1.4)
                    }
                }

                Item {
                    id: seekbarContainer
                    width: parent.width
                    anchors.top: songInfo.bottom
                    anchors.bottom: playbackControls.top
                    anchors.horizontalCenter: parent.horizontalCenter

                    Column {
                        width: parent.width
                        spacing: Theme.spacingXXS
                        anchors.horizontalCenter: parent.horizontalCenter
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.verticalCenterOffset: parent.height * 0.2

                        DankSeekbar {
                            width: parent.width * 0.8
                            height: 20
                            anchors.horizontalCenter: parent.horizontalCenter
                            activePlayer: root.activePlayer
                            stableLength: MprisController.activePlayerStableLength
                            accentColor: MediaAccentService.accent
                            accentTrackColor: MediaAccentService.accentTrack
                            accentSubtleColor: MediaAccentService.accentSubtle
                            isSeeking: root.isSeeking
                            onIsSeekingChanged: root.isSeeking = isSeeking
                        }

                        Item {
                            width: parent.width * 0.8
                            height: 16
                            anchors.horizontalCenter: parent.horizontalCenter

                            StyledText {
                                anchors.left: parent.left
                                anchors.verticalCenter: parent.verticalCenter
                                text: {
                                    if (!activePlayer)
                                        return "0:00";
                                    const rawPos = Math.max(0, activePlayer.position || 0);
                                    const pos = stableLength ? rawPos % Math.max(1, stableLength) : rawPos;
                                    const minutes = Math.floor(pos / 60);
                                    const seconds = Math.floor(pos % 60);
                                    const timeStr = minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
                                    return timeStr;
                                }
                                font.pixelSize: Theme.fontSizeSmall
                                color: Theme.surfaceVariantText
                            }

                            StyledText {
                                anchors.right: parent.right
                                anchors.verticalCenter: parent.verticalCenter
                                text: {
                                    if (!activePlayer || stableLength <= 0)
                                        return "--:--";
                                    const dur = stableLength;
                                    const minutes = Math.floor(dur / 60);
                                    const seconds = Math.floor(dur % 60);
                                    return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
                                }
                                font.pixelSize: Theme.fontSizeSmall
                                color: Theme.surfaceVariantText
                            }
                        }
                    }
                }

                Item {
                    id: playbackControls
                    width: parent.width
                    height: 50
                    anchors.bottom: parent.bottom

                    Row {
                        anchors.centerIn: parent
                        spacing: Theme.spacingM
                        height: parent.height

                        Item {
                            width: 50
                            height: 50
                            anchors.verticalCenter: parent.verticalCenter
                            visible: activePlayer && activePlayer.shuffleSupported

                            Rectangle {
                                width: 40
                                height: 40
                                radius: 20
                                anchors.centerIn: parent
                                color: shuffleArea.containsMouse ? root.accentHover : Theme.withAlpha(root.accent, 0)

                                DankIcon {
                                    anchors.centerIn: parent
                                    name: "shuffle"
                                    size: 20
                                    color: activePlayer && activePlayer.shuffle ? root.accent : Theme.surfaceText
                                }

                                MouseArea {
                                    id: shuffleArea
                                    anchors.fill: parent
                                    hoverEnabled: true
                                    cursorShape: Qt.PointingHandCursor
                                    onClicked: {
                                        if (activePlayer && activePlayer.canControl && activePlayer.shuffleSupported) {
                                            activePlayer.shuffle = !activePlayer.shuffle;
                                        }
                                    }
                                }
                            }
                        }

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

                            Rectangle {
                                width: 40
                                height: 40
                                radius: 20
                                anchors.centerIn: parent
                                color: prevBtnArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : Theme.withAlpha(Theme.surfaceContainerHigh, 0)

                                DankIcon {
                                    anchors.centerIn: parent
                                    name: "skip_previous"
                                    size: 24
                                    color: Theme.surfaceText
                                }

                                MouseArea {
                                    id: prevBtnArea
                                    anchors.fill: parent
                                    hoverEnabled: true
                                    cursorShape: Qt.PointingHandCursor
                                    onClicked: MprisController.previousOrRewind()
                                }
                            }
                        }

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

                            Rectangle {
                                width: 50
                                height: 50
                                radius: 25
                                anchors.centerIn: parent
                                color: root.accent

                                DankIcon {
                                    anchors.centerIn: parent
                                    name: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
                                    size: 28
                                    color: root.onAccent
                                    weight: 500
                                }

                                StateLayer {
                                    id: playPauseArea
                                    disabled: !root.activePlayer || !root.activePlayer.canTogglePlaying
                                    stateColor: root.onAccent
                                    cornerRadius: parent.radius
                                    onClicked: root.activePlayer.togglePlaying()
                                }

                                ElevationShadow {
                                    anchors.fill: parent
                                    z: -1
                                    level: Theme.elevationLevel1
                                    fallbackOffset: 1
                                    targetRadius: parent.radius
                                    targetColor: parent.color
                                    shadowOpacity: Theme.elevationLevel1 && Theme.elevationLevel1.alpha !== undefined ? Theme.elevationLevel1.alpha : 0.2
                                    shadowEnabled: Theme.elevationEnabled
                                }
                            }
                        }

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

                            Rectangle {
                                width: 40
                                height: 40
                                radius: 20
                                anchors.centerIn: parent
                                color: nextBtnArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : Theme.withAlpha(Theme.surfaceContainerHigh, 0)

                                DankIcon {
                                    anchors.centerIn: parent
                                    name: "skip_next"
                                    size: 24
                                    color: Theme.surfaceText
                                }

                                MouseArea {
                                    id: nextBtnArea
                                    anchors.fill: parent
                                    hoverEnabled: true
                                    cursorShape: Qt.PointingHandCursor
                                    onClicked: MprisController.next()
                                }
                            }
                        }

                        Item {
                            width: 50
                            height: 50
                            anchors.verticalCenter: parent.verticalCenter
                            visible: activePlayer && activePlayer.loopSupported

                            Rectangle {
                                width: 40
                                height: 40
                                radius: 20
                                anchors.centerIn: parent
                                color: repeatArea.containsMouse ? root.accentHover : Theme.withAlpha(root.accent, 0)

                                DankIcon {
                                    anchors.centerIn: parent
                                    name: {
                                        if (!activePlayer)
                                            return "repeat";
                                        switch (activePlayer.loopState) {
                                        case MprisLoopState.Track:
                                            return "repeat_one";
                                        case MprisLoopState.Playlist:
                                            return "repeat";
                                        default:
                                            return "repeat";
                                        }
                                    }
                                    size: 20
                                    color: activePlayer && activePlayer.loopState !== MprisLoopState.None ? root.accent : Theme.surfaceText
                                }

                                MouseArea {
                                    id: repeatArea
                                    anchors.fill: parent
                                    hoverEnabled: true
                                    cursorShape: Qt.PointingHandCursor
                                    onClicked: {
                                        if (activePlayer && activePlayer.canControl && activePlayer.loopSupported) {
                                            switch (activePlayer.loopState) {
                                            case MprisLoopState.None:
                                                activePlayer.loopState = MprisLoopState.Playlist;
                                                break;
                                            case MprisLoopState.Playlist:
                                                activePlayer.loopState = MprisLoopState.Track;
                                                break;
                                            case MprisLoopState.Track:
                                                activePlayer.loopState = MprisLoopState.None;
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    Rectangle {
        id: playerSelectorButton
        width: 40
        height: 40
        radius: 20
        x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
        y: 185
        color: playerSelectorArea.containsMouse || playersExpanded ? root.accentPressed : Theme.withAlpha(root.accentPressed, 0)
        border.color: Theme.outlineStrong
        border.width: 1
        z: 100
        visible: (allPlayers?.length || 0) >= 1

        DankIcon {
            anchors.centerIn: parent
            name: "assistant_device"
            size: 18
            color: Theme.surfaceText
        }

        MouseArea {
            id: playerSelectorArea
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            onClicked: {
                if (playersExpanded) {
                    const players = (root.allPlayers || []).filter(p => p && !MprisController.isIdle(p));
                    if (players.length > 1) {
                        let currentIndex = -1;
                        for (let i = 0; i < players.length; i++) {
                            if (players[i] === root.activePlayer) {
                                currentIndex = i;
                                break;
                            }
                        }
                        const nextIndex = (currentIndex + 1) % players.length;
                        MprisController.setActivePlayer(players[nextIndex]);
                    }
                    return;
                }
                hideDropdowns();
                playersExpanded = true;
                const buttonsOnRight = !isRightEdge;
                const btnY = playerSelectorButton.y + playerSelectorButton.height / 2;
                const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
                const screenY = popoutY + contentOffsetY + btnY;
                showPlayersDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
            }
            onEntered: {
                dropdownButtonEntered();
                if (playersExpanded)
                    return;
                hideDropdowns();
                playersExpanded = true;
                const buttonsOnRight = !isRightEdge;
                const btnY = playerSelectorButton.y + playerSelectorButton.height / 2;
                const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
                const screenY = popoutY + contentOffsetY + btnY;
                showPlayersDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
            }
            onExited: {
                if (playersExpanded)
                    dropdownButtonExited();
            }
        }
    }

    Rectangle {
        id: volumeButton
        width: 40
        height: 40
        radius: 20
        x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
        y: 130
        color: volumeButtonArea.containsMouse && volumeAvailable || volumeExpanded ? root.accentPressed : Theme.withAlpha(root.accentPressed, 0)
        border.color: volumeAvailable ? Theme.outlineStrong : Theme.outlineMedium
        border.width: 1
        z: 101
        enabled: volumeAvailable

        property real previousVolume: 0.0

        DankIcon {
            anchors.centerIn: parent
            name: getVolumeIcon()
            size: 18
            color: volumeAvailable && currentVolume > 0 ? root.accent : Theme.withAlpha(Theme.surfaceText, volumeAvailable ? 1.0 : 0.5)
        }

        MouseArea {
            id: volumeButtonArea
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            onEntered: {
                dropdownButtonEntered();
                if (volumeExpanded)
                    return;
                hideDropdowns();
                volumeExpanded = true;
                const buttonsOnRight = !isRightEdge;
                const btnY = volumeButton.y + volumeButton.height / 2;
                const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
                const screenY = popoutY + contentOffsetY + btnY;
                showVolumeDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
            }
            onExited: {
                if (volumeExpanded)
                    dropdownButtonExited();
            }
            onClicked: {
                toggleMute();
            }
            property real wheelAccum: 0
            onWheel: wheelEvent => {
                wheelEvent.accepted = true;
                wheelAccum += wheelEvent.angleDelta.y;
                const notches = wheelAccum > 0 ? Math.floor(wheelAccum / 120) : Math.ceil(wheelAccum / 120);
                if (notches === 0)
                    return;
                wheelAccum -= notches * 120;
                root.adjustVolume(notches * AudioService.wheelVolumeStep);
            }
        }
    }

    Rectangle {
        id: audioDevicesButton
        width: 40
        height: 40
        radius: 20
        x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
        y: 240
        color: audioDevicesArea.containsMouse || devicesExpanded ? root.accentPressed : Theme.withAlpha(root.accentPressed, 0)
        border.color: Theme.outlineStrong
        border.width: 1
        z: 100

        DankIcon {
            anchors.centerIn: parent
            name: "speaker"
            size: 18
            color: Theme.surfaceText
        }

        MouseArea {
            id: audioDevicesArea
            anchors.fill: parent
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            acceptedButtons: Qt.LeftButton | Qt.RightButton
            onPressed: mouse => {
                if (mouse.button === Qt.RightButton) {
                    mouse.accepted = true;
                }
            }
            onWheel: wheelEvent => {
                const delta = wheelEvent.angleDelta.y;
                if (delta !== 0) {
                    AudioService.cycleAudioOutputDirection(delta < 0);
                    wheelEvent.accepted = true;
                }
            }
            onClicked: mouse => {
                if (mouse.button === Qt.RightButton) {
                    if (AudioService.sink?.audio) {
                        SessionData.suppressOSDTemporarily();
                        AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
                    }
                    return;
                }
                if (devicesExpanded) {
                    const sinks = AudioService.getAvailableSinks();
                    if (sinks && sinks.length > 1) {
                        let currentIndex = -1;
                        for (let i = 0; i < sinks.length; i++) {
                            if (sinks[i]?.name === AudioService.sink?.name) {
                                currentIndex = i;
                                break;
                            }
                        }
                        const nextIndex = (currentIndex + 1) % sinks.length;
                        AudioService.setSink(sinks[nextIndex]);
                    }
                    return;
                }
                hideDropdowns();
                devicesExpanded = true;
                const buttonsOnRight = !isRightEdge;
                const btnY = audioDevicesButton.y + audioDevicesButton.height / 2;
                const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
                const screenY = popoutY + contentOffsetY + btnY;
                showAudioDevicesDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight);
            }
            onEntered: {
                dropdownButtonEntered();
                if (devicesExpanded)
                    return;
                hideDropdowns();
                devicesExpanded = true;
                const buttonsOnRight = !isRightEdge;
                const btnY = audioDevicesButton.y + audioDevicesButton.height / 2;
                const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
                const screenY = popoutY + contentOffsetY + btnY;
                showAudioDevicesDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight);
            }
            onExited: {
                if (devicesExpanded)
                    dropdownButtonExited();
            }
        }
    }
}
