import QtQuick
import Quickshell
import Quickshell.Services.Mpris
import qs.Utils
pragma Singleton

Singleton {
    //Logger.i("Media", "No active player found")

    id: root

    property var currentPlayer: null
    property string playerIdentity: currentPlayer ? (currentPlayer.identity || "") : ""
    property real currentPosition: 0
    property bool isSeeking: false
    property int selectedPlayerIndex: 0
    property bool isPlaying: currentPlayer ? (currentPlayer.playbackState === MprisPlaybackState.Playing || currentPlayer.isPlaying) : false
    property string trackTitle: currentPlayer ? (currentPlayer.trackTitle !== undefined ? currentPlayer.trackTitle.replace(/(\r\n|\n|\r)/g, "") : "") : ""
    property string trackArtist: currentPlayer ? (currentPlayer.trackArtist || "") : ""
    property string trackAlbum: currentPlayer ? (currentPlayer.trackAlbum || "") : ""
    property string trackArtUrl: currentPlayer ? (currentPlayer.trackArtUrl || "") : ""
    property real trackLength: currentPlayer ? ((currentPlayer.length < infiniteTrackLength) ? currentPlayer.length : 0) : 0
    property bool canPlay: currentPlayer ? currentPlayer.canPlay : false
    property bool canPause: currentPlayer ? currentPlayer.canPause : false
    property bool canGoNext: currentPlayer ? currentPlayer.canGoNext : false
    property bool canGoPrevious: currentPlayer ? currentPlayer.canGoPrevious : false
    property bool canSeek: currentPlayer ? currentPlayer.canSeek : false
    property string positionString: formatTime(currentPosition)
    property string lengthString: formatTime(trackLength)
    property real infiniteTrackLength: 9.22337e+11
    property bool autoSwitchingPaused: false

    function formatTime(seconds) {
        if (isNaN(seconds) || seconds < 0)
            return "0:00";

        var h = Math.floor(seconds / 3600);
        var m = Math.floor((seconds % 3600) / 60);
        var s = Math.floor(seconds % 60);
        var pad = function pad(n) {
            return (n < 10) ? ("0" + n) : n;
        };
        if (h > 0)
            return h + ":" + pad(m) + ":" + pad(s);
        else
            return m + ":" + pad(s);
    }

    function getAvailablePlayers() {
        if (!Mpris.players || !Mpris.players.values)
            return [];

        let allPlayers = Mpris.players.values;
        let finalPlayers = [];
        const genericBrowsers = ["firefox", "chromium", "chrome"];
        const blacklist = [];
        // Separate players into specific and generic lists
        let specificPlayers = [];
        let genericPlayers = [];
        for (var i = 0; i < allPlayers.length; i++) {
            const identity = String(allPlayers[i].identity || "").toLowerCase();
            const name = String(allPlayers[i].name || "").toLowerCase();
            const match = blacklist.find((b) => {
                const s = String(b || "").toLowerCase();
                return s && (identity.includes(s) || name.includes(s));
            });
            if (match)
                continue;

            if (genericBrowsers.some((b) => {
                return identity.includes(b);
            }))
                genericPlayers.push(allPlayers[i]);
            else
                specificPlayers.push(allPlayers[i]);
        }
        let matchedGenericIndices = {
        };
        // For each specific player, try to find and pair it with a generic partner
        for (var i = 0; i < specificPlayers.length; i++) {
            let specificPlayer = specificPlayers[i];
            let title1 = String(specificPlayer.trackTitle || "").trim();
            let wasMatched = false;
            if (title1) {
                for (var j = 0; j < genericPlayers.length; j++) {
                    if (matchedGenericIndices[j])
                        continue;

                    let genericPlayer = genericPlayers[j];
                    let title2 = String(genericPlayer.trackTitle || "").trim();
                    if (title2 && (title1.includes(title2) || title2.includes(title1))) {
                        let dataPlayer = genericPlayer;
                        let identityPlayer = specificPlayer;
                        let scoreSpecific = (specificPlayer.trackArtUrl ? 1 : 0);
                        let scoreGeneric = (genericPlayer.trackArtUrl ? 1 : 0);
                        if (scoreSpecific > scoreGeneric)
                            dataPlayer = specificPlayer;

                        let virtualPlayer = {
                            "identity": identityPlayer.identity,
                            "desktopEntry": identityPlayer.desktopEntry,
                            "trackTitle": dataPlayer.trackTitle,
                            "trackArtist": dataPlayer.trackArtist,
                            "trackAlbum": dataPlayer.trackAlbum,
                            "trackArtUrl": dataPlayer.trackArtUrl,
                            "length": dataPlayer.length || 0,
                            "position": dataPlayer.position || 0,
                            "playbackState": dataPlayer.playbackState,
                            "isPlaying": dataPlayer.isPlaying || false,
                            "canPlay": dataPlayer.canPlay || false,
                            "canPause": dataPlayer.canPause || false,
                            "canGoNext": dataPlayer.canGoNext || false,
                            "canGoPrevious": dataPlayer.canGoPrevious || false,
                            "canSeek": dataPlayer.canSeek || false,
                            "canControl": dataPlayer.canControl || false,
                            "_stateSource": dataPlayer,
                            "_controlTarget": identityPlayer
                        };
                        finalPlayers.push(virtualPlayer);
                        matchedGenericIndices[j] = true;
                        wasMatched = true;
                        break;
                    }
                }
            }
            if (!wasMatched)
                finalPlayers.push(specificPlayer);

        }
        // Add any generic players that were not matched
        for (var i = 0; i < genericPlayers.length; i++) {
            if (!matchedGenericIndices[i])
                finalPlayers.push(genericPlayers[i]);

        }
        // Filter for controllable players
        let controllablePlayers = [];
        for (var i = 0; i < finalPlayers.length; i++) {
            let player = finalPlayers[i];
            if (player && player.canPlay)
                controllablePlayers.push(player);

        }
        return controllablePlayers;
    }

    function findActivePlayer() {
        let availablePlayers = getAvailablePlayers();
        if (availablePlayers.length === 0)
            return null;

        // Prioritize the actively playing player ---
        for (var i = 0; i < availablePlayers.length; i++) {
            if (availablePlayers[i] && availablePlayers[i].playbackState === MprisPlaybackState.Playing) {
                Logger.d("Media", "Found actively playing player: " + availablePlayers[i].identity);
                selectedPlayerIndex = i;
                return availablePlayers[i];
            }
        }
        // fallback if nothing is playing)
        if (selectedPlayerIndex < availablePlayers.length) {
            return availablePlayers[selectedPlayerIndex];
        } else {
            selectedPlayerIndex = 0;
            return availablePlayers[0];
        }
    }

    function switchToPlayer(index) {
        let availablePlayers = getAvailablePlayers();
        if (index >= 0 && index < availablePlayers.length) {
            let newPlayer = availablePlayers[index];
            if (newPlayer !== currentPlayer) {
                currentPlayer = newPlayer;
                selectedPlayerIndex = index;
                currentPosition = currentPlayer ? currentPlayer.position : 0;
                autoSwitchingPaused = true;
                Logger.d("Media", "Manually switched to player " + currentPlayer.identity);
            }
        }
    }

    // Switch to the most recently active player
    function updateCurrentPlayer() {
        let newPlayer = findActivePlayer();
        if (newPlayer !== currentPlayer) {
            currentPlayer = newPlayer;
            currentPosition = currentPlayer ? currentPlayer.position : 0;
        }
    }

    function toggleAutoSwitchingPaused() {
        if (autoSwitchingPaused) {
            autoSwitchingPaused = false;
            updateCurrentPlayer();
        } else {
            autoSwitchingPaused = true;
        }
    }

    function playPause() {
        if (currentPlayer) {
            let stateSource = currentPlayer._stateSource || currentPlayer;
            let controlTarget = currentPlayer._controlTarget || currentPlayer;
            if (stateSource.playbackState === MprisPlaybackState.Playing)
                controlTarget.pause();
            else
                controlTarget.play();
        }
    }

    function play() {
        let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
        if (target && target.canPlay)
            target.play();

    }

    function stop() {
        let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
        if (target)
            target.stop();

    }

    function pause() {
        let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
        if (target && target.canPause)
            target.pause();

    }

    function next() {
        let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
        if (target && target.canGoNext)
            target.next();

    }

    function previous() {
        let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
        if (target && target.canGoPrevious)
            target.previous();

    }

    function seek(position) {
        let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
        if (target && target.canSeek) {
            target.position = position;
            currentPosition = position;
        }
    }

    function seekRelative(offset) {
        let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
        if (target && target.canSeek && target.length > 0) {
            let seekPosition = target.position + offset;
            target.position = seekPosition;
            currentPosition = seekPosition;
        }
    }

    // Seek to position based on ratio (0.0 to 1.0)
    function seekByRatio(ratio) {
        let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
        if (target && target.canSeek && target.length > 0) {
            let seekPosition = ratio * target.length;
            target.position = seekPosition;
            currentPosition = seekPosition;
        }
    }

    Component.onCompleted: {
        updateCurrentPlayer();
    }
    // Reset position when switching to inactive player
    onCurrentPlayerChanged: {
        if (!currentPlayer || !currentPlayer.isPlaying || currentPlayer.playbackState !== MprisPlaybackState.Playing)
            currentPosition = 0;

    }

    // Update progress bar every second while playing
    Timer {
        id: positionTimer

        interval: 1000
        running: currentPlayer && !root.isSeeking && currentPlayer.isPlaying && currentPlayer.length > 0 && currentPlayer.playbackState === MprisPlaybackState.Playing
        repeat: true
        onTriggered: {
            if (currentPlayer && !root.isSeeking && currentPlayer.isPlaying && currentPlayer.playbackState === MprisPlaybackState.Playing)
                currentPosition = currentPlayer.position;
            else
                running = false;
        }
    }

    // Avoid overwriting currentPosition while seeking due to backend position changes
    Connections {
        function onPositionChanged() {
            if (!root.isSeeking && currentPlayer)
                currentPosition = currentPlayer.position;

        }

        function onPlaybackStateChanged() {
            if (!root.isSeeking && currentPlayer)
                currentPosition = currentPlayer.position;

        }

        target: currentPlayer ? (currentPlayer._stateSource || currentPlayer) : null
    }

    Timer {
        id: playerStateMonitor

        interval: 2000 // Check every 2 seconds
        repeat: true
        running: true
        onTriggered: {
            //Logger.d("MediaService", "playerStateMonitor triggered. autoSwitchingPaused: " + root.autoSwitchingPaused)
            if (autoSwitchingPaused)
                return ;

            // Only update if we don't have a playing player or if current player is paused
            if (!currentPlayer || !currentPlayer.isPlaying || currentPlayer.playbackState !== MprisPlaybackState.Playing)
                updateCurrentPlayer();

        }
    }

    // Update current player when available players change
    Connections {
        function onValuesChanged() {
            Logger.d("Media", "Players changed");
            updateCurrentPlayer();
        }

        target: Mpris.players
    }

}
