/**
 * Premium Radio Tuner - Frontend Audio Controller
 */
(function($) {
    "use strict";

    if (window.plugtuner_radio_initialized) {
        return;
    }
    window.plugtuner_radio_initialized = true;

    // Global Audio States
    window.plugtuner_tuner = window.plugtuner_tuner || {};

    // Remove duplicate players
    function removeDuplicatePlayers() {
        const cards = document.querySelectorAll('.floating-player-card.rtp-floating-player');
        if (cards.length <= 1) return;

        let initializedCard = null;
        for (let i = 0; i < cards.length; i++) {
            if (cards[i].classList.contains('rtp-initialized')) {
                initializedCard = cards[i];
                break;
            }
        }

        if (initializedCard) {
            cards.forEach(card => {
                if (card !== initializedCard) {
                    card.remove();
                }
            });
        } else {
            // Keep the first one, remove others
            for (let i = 1; i < cards.length; i++) {
                cards[i].remove();
            }
        }
    }
    window.plugtuner_tuner.removeDuplicatePlayers = removeDuplicatePlayers;

    // Setup MutationObserver to automatically remove duplicate player cards on dynamic page load
    function setupDuplicateObserver() {
        if (typeof MutationObserver === 'undefined') return;

        const observer = new MutationObserver((mutations) => {
            const initializedCard = document.querySelector('.floating-player-card.rtp-floating-player.rtp-initialized');
            if (!initializedCard) return;

            mutations.forEach((mutation) => {
                if (!mutation.addedNodes) return;
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType !== Node.ELEMENT_NODE) return;

                    // If the added node itself is a player card and not the initialized one, remove it
                    if (node.matches('.floating-player-card.rtp-floating-player') && node !== initializedCard) {
                        node.remove();
                        return;
                    }

                    // Look for nested player cards inside the added node
                    const duplicates = node.querySelectorAll('.floating-player-card.rtp-floating-player');
                    duplicates.forEach((dup) => {
                        if (dup !== initializedCard) {
                            dup.remove();
                        }
                    });
                });
            });
        });

        observer.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    }

    setupDuplicateObserver();
    let audioPlayer = new Audio();
    window.plugtuner_tuner.audioPlayer = audioPlayer;
    let isPlaying = false;
    let stations = [];
    let customHeights = [50, 50, 50, 50, 50, 50, 50, 50];
    let nowPlayingInterval = null;
    let currentPlayingUrl = '';

    // Web Audio Equalizer & VU States
    let audioCtx = null;
    let audioSource = null;
    let eqFilters = [];
    const eqFrequencies = [60, 150, 400, 1000, 2000, 2400, 6000, 15000];
    let isCorsBypassed = false;
    let splitterNode = null;
    let analyserLeft = null;
    let analyserRight = null;
    let vuAnimationId = null;
    let currentAngleLeft = -50;
    let currentAngleRight = -50;
    let currentBand = 'FM';

    // Tuning Sound Effect (Absolute Plugin Path)
    const tuningAudio = new Audio(plugtuner_public_vars.plugin_url + 'public/sound/140157__copyc4t__fm-radio-tuning-sweeps.mp3');
    tuningAudio.loop = true;
    window.plugtuner_tuner.tuningAudio = tuningAudio;

    // Tuning & Scale Configuration
    let minFreq = 88.0;
    let maxFreq = 108.0;
    let currentFreq = 96.7; // Default initial frequency
    let isSearching = false;
    let searchDirection = 1;
    let searchInterval = null;
    let searchSpeed = 0.1;

    // Position tuner scale labels dynamically
    function initScaleLabels() {
        const containers = document.querySelectorAll('.tuner-labels');
        containers.forEach(container => {
            const labels = container.querySelectorAll('span');
            labels.forEach((label, index) => {
                label.style.left = `${index * 10}%`;
            });
        });
    }
    window.plugtuner_tuner.initScaleLabels = initScaleLabels;

    // Connect equalizer filters
    function connectEqualizerFilters() {
        if (!audioCtx || !audioSource) return;

        try {
            audioSource.disconnect();
            eqFilters.forEach(f => {
                try { f.disconnect(); } catch (e) {}
            });
            if (splitterNode) {
                try { splitterNode.disconnect(); } catch (e) {}
            }
        } catch (e) {
        }

        let isFreeTheme = false;
        const playerCards = document.querySelectorAll('.floating-player-card');
        playerCards.forEach(card => {
            if (card.classList.contains('rtp-design-free-light') || card.classList.contains('rtp-design-free-dark')) {
                isFreeTheme = true;
            }
        });

        if (isFreeTheme) {
            if (!isCorsBypassed) {
                try {
                    if (!analyserLeft) {
                        analyserLeft = audioCtx.createAnalyser();
                        analyserLeft.fftSize = 64; // 32 frequency bins, perfect for 20 bars
                    }
                    audioSource.connect(analyserLeft);
                    analyserLeft.connect(audioCtx.destination);
                    return;
                } catch (e) {
                    console.error("Failed to connect free-light visualizer analyser:", e);
                }
            }
            audioSource.connect(audioCtx.destination);
            return;
        }

        if (isCorsBypassed) {
            audioSource.connect(audioCtx.destination);
            return;
        }

        if (eqFilters.length === 0) {
            eqFrequencies.forEach((freq) => {
                const filter = audioCtx.createBiquadFilter();
                filter.type = "peaking";
                filter.frequency.value = freq;
                filter.Q.value = 1.0;
                eqFilters.push(filter);
            });
        }

        // Sync gains
        eqFilters.forEach((filter, index) => {
            const height = customHeights[index] !== undefined ? customHeights[index] : 50;
            filter.gain.setValueAtTime((height - 50) * (12 / 40), audioCtx.currentTime);
        });

        let lastNode = audioSource;
        eqFilters.forEach(filter => {
            lastNode.connect(filter);
            lastNode = filter;
        });
        lastNode.connect(audioCtx.destination);

        // Connect Analysers for VU or FM-AM themes
        const activeCards = document.querySelectorAll('.floating-player-card');
        let hasVUTheme = false;
        activeCards.forEach(card => {
            card.classList.forEach(cls => {
                if (cls.startsWith('rtp-design-') && (cls.includes('vu-') || cls.includes('fm-am-'))) {
                    hasVUTheme = true;
                }
            });
        });

        if (hasVUTheme && !isCorsBypassed) {
            try {
                if (!splitterNode) {
                    splitterNode = audioCtx.createChannelSplitter(2);
                    analyserLeft = audioCtx.createAnalyser();
                    analyserRight = audioCtx.createAnalyser();
                    analyserLeft.fftSize = 256;
                    analyserRight.fftSize = 256;
                }
                
                lastNode.connect(splitterNode);
                splitterNode.connect(analyserLeft, 0); // left channel
                splitterNode.connect(analyserRight, 1); // right channel
            } catch (e) {
            }
        }
    }

    /**
     * iOS keeps Web Audio asleep in two separate ways, and both of them left the
     * VU needles and the equalizer dead on iPad while the stream itself played.
     *
     * 1. WebKit will not start a context that was created in the same tick as the
     *    gesture trying to start it - resume() there is silently ignored. So the
     *    context is built on touchstart and unlocked on touchend, two ticks of
     *    the same tap, and unlocking pushes one frame of silence through it,
     *    which is what WebKit actually waits for.
     * 2. The context is interrupted again whenever the page loses the audio
     *    session - the media element grabbing it on play, Siri, a call, another
     *    tab, backgrounding. Playback survives, the graph does not, and nothing
     *    resumes it on its own.
     */
    function unlockAudioContext() {
        if (!audioCtx || audioCtx.state === "running") return;

        audioCtx.resume();

        try {
            const buf = audioCtx.createBuffer(1, 1, 22050);
            const src = audioCtx.createBufferSource();
            src.buffer = buf;
            src.connect(audioCtx.destination);
            src.start(0);
        } catch (e) {
            // Nothing to recover here; the watchdog below keeps trying.
        }
    }

    function keepAudioContextRunning() {
        if (!audioCtx || !isPlaying) return;
        if (audioCtx.state === "suspended") {
            audioCtx.resume();
        }
    }

    // Capture phase, so these run before the player own click handler.
    document.addEventListener("touchstart", function () {
        if (!audioCtx) initAudioContext();
    }, true);
    ["touchend", "pointerup", "mouseup", "keydown"].forEach(function (ev) {
        document.addEventListener(ev, unlockAudioContext, true);
    });

    document.addEventListener("visibilitychange", function () {
        if (!document.hidden) keepAudioContextRunning();
    });
    window.addEventListener("focus", keepAudioContextRunning);
    window.addEventListener("pageshow", keepAudioContextRunning);

    // Covers the race where iOS interrupts the context a moment after the media
    // element takes the audio session, which lands just after the unlocking tap.
    // resume() on a running context is a no-op.
    setInterval(keepAudioContextRunning, 1000);

    // Initialize Web Audio context
    function initAudioContext() {
        if (audioCtx) return;

        const AudioContextClass = window.AudioContext || window.webkitAudioContext;
        audioCtx = new AudioContextClass();

        audioCtx.onstatechange = keepAudioContextRunning;

        if (!audioSource) {
            audioSource = audioCtx.createMediaElementSource(audioPlayer);
        }

        connectEqualizerFilters();
    }

    // Reconnection State Variables
    let reconnectTimeout = null;
    let playRetryTimeout = null;

    function clearReconnectTimeouts() {
        if (reconnectTimeout) {
            clearTimeout(reconnectTimeout);
            reconnectTimeout = null;
        }
        if (playRetryTimeout) {
            clearTimeout(playRetryTimeout);
            playRetryTimeout = null;
        }
    }

    function attemptReconnect(player) {
        clearReconnectTimeouts();
        if (!isPlaying) return;

        const currentUrl = player.src;
        if (!currentUrl) return;

        player.src = currentUrl;
        player.load();
        player.play()
            .then(() => {
            })
            .catch(err => {
                console.error('Reconnection attempt failed:', err);
                if (isPlaying) {
                    reconnectTimeout = setTimeout(() => {
                        attemptReconnect(player);
                    }, 3000);
                }
            });
    }

    function attemptPlayWithRetry(player) {
        clearReconnectTimeouts();
        if (!isPlaying) return;

        player.play()
            .then(() => {
            })
            .catch(err => {
                console.error('Playback resume failed:', err);
                if (err.name === 'NotAllowedError') {
                    setPlayState(false);
                    stopNowPlayingPolling();
                    return;
                }
                if (isPlaying) {
                    playRetryTimeout = setTimeout(() => {
                        attemptReconnect(player);
                    }, 2000);
                }
            });
    }

    // Bind events to player
    function bindAudioEvents(player) {
        player.addEventListener('playing', () => {
            if (player.isDiscarded) return;
            setPlayState(true);
            saveSessionState();
            startNowPlayingPolling(player.src);
            keepAudioContextRunning();
        });

        player.addEventListener('pause', () => {
            if (player.isDiscarded) return;
            if (!isPlaying) {
                // User-initiated pause (since isPlaying was set to false beforehand)
                saveSessionState();
                stopNowPlayingPolling();
            } else {
                // Involuntary pause (network drop, buffer stall, OS event)
                attemptPlayWithRetry(player);
            }
        });

        player.addEventListener('ended', () => {
            if (player.isDiscarded) return;
            if (isPlaying) {
                attemptReconnect(player);
            }
        });

        player.addEventListener('error', () => {
            if (player.isDiscarded) return;
            console.error('Audio stream error, code:', player.error ? player.error.code : 'unknown');
            
            // Check for Mixed Content block (HTTP stream on HTTPS site)
            if (window.location.protocol === 'https:' && player.src && player.src.startsWith('http://')) {
                const trackDescs = document.querySelectorAll('.track-artist-desc');
                trackDescs.forEach(d => {
                    d.innerHTML = `<span style="color:#ef4444; font-weight:bold;">⚠️ Insecure HTTP stream blocked on HTTPS site.</span>`;
                });
                const minimizedDescs = document.querySelectorAll('.rtp-minimized-desc');
                minimizedDescs.forEach(d => {
                    d.innerHTML = `<span style="color:#ef4444;">⚠️ Blocked (HTTP on HTTPS)</span>`;
                });
                clearReconnectTimeouts();
                setPlayState(false);
                return;
            }

            // CORS Fallback: If player fails with CORS, recreate without CORS
            if (player.crossOrigin === "anonymous" && !isCorsBypassed) {
                isCorsBypassed = true;
                
                const currentUrl = player.src;
                const wasPlaying = isPlaying;

                recreateAudioPlayer(true, currentUrl);
                audioPlayer.load();

                if (wasPlaying || plugtuner_public_vars.enable_autoplay === '1') {
                    attemptPlayWithRetry(audioPlayer);
                }
                return;
            }

            // Auto-reconnect fallback for other stream drops
            if (isPlaying) {
                attemptReconnect(player);
            }
        });

        player.addEventListener('volumechange', () => {
            if (player.isDiscarded) return;
            saveSessionState();
        });
    }

    /**
     * Force a stream URL onto https.
     *
     * An http stream on an https site is mixed content: the padlock disappears
     * and stricter browsers refuse the stream outright. Practically every
     * station serves the same mount over TLS, so the upgrade is free. An
     * explicit :80 has to go too, since it would contradict the scheme.
     *
     * No fallback to http, deliberately - a station that cannot do TLS should
     * not be added at all, rather than costing every visitor the padlock.
     */
    function toSecureStreamUrl(url) {
        if (!url || !/^http:\/\//i.test(url)) return url;
        return url
            .replace(/^http:\/\//i, "https://")
            .replace(/^(https:\/\/[^\/:]+):80(?=[\/?]|$)/i, "$1");
    }

    // Recreate audio element (useful on CORS errors to reset context routing)
    function recreateAudioPlayer(bypassCors, src) {
        if (audioPlayer) {
            audioPlayer.isDiscarded = true;
            audioPlayer.pause();
            audioPlayer.src = '';
        }
        stopNowPlayingPolling();

        audioPlayer = new Audio();
        window.plugtuner_tuner.audioPlayer = audioPlayer;

        const volumeSlider = document.querySelector('.volume-slider');
        if (volumeSlider) {
            audioPlayer.volume = volumeSlider.value / 100;
            tuningAudio.volume = volumeSlider.value / 100;
        }

        if (!bypassCors) {
            audioPlayer.crossOrigin = "anonymous";
        }

        if (src) {
            audioPlayer.src = toSecureStreamUrl(src);
        }

        bindAudioEvents(audioPlayer);

        if (!bypassCors && audioCtx && audioCtx.state !== 'closed') {
            try {
                audioSource = audioCtx.createMediaElementSource(audioPlayer);
                connectEqualizerFilters();
            } catch (e) {
                console.error('Error connecting source node:', e);
            }
        }
    }

    // Play station stream (and increment stats via server AJAX)
    function playStationStream(url) {
        if (!audioCtx) {
            initAudioContext();
        }
        if (audioCtx && audioCtx.state === "suspended") {
            audioCtx.resume();
        }

        isCorsBypassed = false;
        recreateAudioPlayer(false, url);

        isPlaying = true;
        audioPlayer.play()
            .then(() => {
                setPlayState(true);
                saveSessionState();
                
                // Increment plays count on the server database
                $.post(plugtuner_public_vars.ajax_url, {
                    action: 'plugtuner_increment_plays',
                    nonce: plugtuner_public_vars.nonce,
                    stream_url: url
                }, function(response) {
                    if (response.success) {
                        // Dynamically update count in local array if needed
                        const station = stations.find(s => s.url === url);
                        if (station) {
                            station.plays = response.data.plays;
                        }
                    }
                });
            })
            .catch(err => {
                console.error('playStationStream failed:', err);
                if (!audioPlayer.isDiscarded) {
                    setPlayState(false);
                }
            });
    }
    window.plugtuner_tuner.playStationStream = playStationStream;

    // Bind default events
    bindAudioEvents(audioPlayer);

    // Update Player UI states
    function setPlayState(playing) {
        isPlaying = playing;
        
        if (!playing) {
            clearReconnectTimeouts();
        }
        
        const playerPlayBtns = document.querySelectorAll('.player-play-btn');
        playerPlayBtns.forEach(playerPlayBtn => {
            if (playing) {
                playerPlayBtn.innerHTML = '<i class="fa-solid fa-pause"></i>';
                playerPlayBtn.classList.add('playing');
            } else {
                playerPlayBtn.innerHTML = '<i class="fa-solid fa-play"></i>';
                playerPlayBtn.classList.remove('playing');
            }
        });

        const minimizedPlayBtns = document.querySelectorAll('.rtp-minimized-play-btn');
        minimizedPlayBtns.forEach(btn => {
            if (playing) {
                btn.innerHTML = '<i class="fa-solid fa-pause"></i>';
            } else {
                btn.innerHTML = '<i class="fa-solid fa-play"></i>';
            }
        });

        const playerCards = document.querySelectorAll('.floating-player-card');
        playerCards.forEach(playerCard => {
            if (playing) {
                playerCard.classList.add('playing');
                playerCard.classList.add('rtp-playing');
            } else {
                playerCard.classList.remove('playing');
                playerCard.classList.remove('rtp-playing');
            }
        });

        if (playing) {
            startEqualizerJSEffect();
        } else {
            stopEqualizerJSEffect();
        }

        if (playing) {
            updateEqStatus(isCorsBypassed ? 'bypassed' : 'active');
        } else {
            updateEqStatus('suspended');
        }

        if (window.plugtuner_tuner && typeof window.plugtuner_tuner.syncStationButtonsUI === 'function') {
            window.plugtuner_tuner.syncStationButtonsUI(playing);
        }
    }
    window.plugtuner_tuner.setPlayState = setPlayState;

    function updateEqStatus(status) {
        const indicators = document.querySelectorAll('.eq-status-indicator');
        if (indicators.length === 0) return;

        indicators.forEach(indicator => {
            const card = indicator.closest('.floating-player-card');
            if (card && (card.classList.contains('rtp-design-free-light') || card.classList.contains('rtp-design-free-dark'))) {
                const textSpan = indicator.querySelector('.status-text');
                if (textSpan) textSpan.innerText = 'Audio Visualizer';
                indicator.classList.remove('bypassed');
                indicator.classList.add('active');
                return;
            }

            const textSpan = indicator.querySelector('.status-text');
            indicator.classList.remove('active', 'bypassed');
            
            if (status === 'active') {
                indicator.classList.add('active');
                if (textSpan) textSpan.innerText = 'EQ Active';
            } else if (status === 'bypassed') {
                indicator.classList.add('bypassed');
                if (textSpan) textSpan.innerText = 'EQ Bypassed (CORS)';
            } else {
                if (textSpan) textSpan.innerText = 'EQ Off';
            }
        });
    }

    function startEqualizerJSEffect() {
        const cards = document.querySelectorAll('.floating-player-card');
        cards.forEach(card => {
            const handles = card.querySelectorAll('.equalizer-handle, .rtp-vu-equalizer-handle');
            handles.forEach((handle, index) => {
                handle.style.bottom = `${customHeights[index]}%`;
            });
        });
    }

    function stopEqualizerJSEffect() {
        const cards = document.querySelectorAll('.floating-player-card');
        cards.forEach(card => {
            const handles = card.querySelectorAll('.equalizer-handle, .rtp-vu-equalizer-handle');
            handles.forEach((handle, index) => {
                handle.style.bottom = `${customHeights[index]}%`;
            });
        });
    }

    // Tuner searching dial scan
    function startSearch(direction) {
        if (isSearching) return;

        if (direction === -1 && currentFreq <= minFreq) return;
        if (direction === 1 && currentFreq >= maxFreq) return;

        isSearching = true;
        searchDirection = direction;

        audioPlayer.pause();
        audioPlayer.removeAttribute('src');
        audioPlayer.load();
        setPlayState(false);

        tuningAudio.volume = audioPlayer.volume;
        tuningAudio.play().catch(function() { /* autoplay policy may block the effect */ });

        searchInterval = setInterval(() => {
            currentFreq += searchSpeed * searchDirection;

            if (currentFreq >= maxFreq && searchDirection === 1) {
                currentFreq = maxFreq;
                rebindRadioUI();
                stopSearch(null);
                return;
            }
            if (currentFreq <= minFreq && searchDirection === -1) {
                currentFreq = minFreq;
                rebindRadioUI();
                stopSearch(null);
                return;
            }

            rebindRadioUI();

            const epsilon = currentBand === 'AM' ? 1.0 : (searchSpeed / 1.5);
            const bandStations = getActiveBandStations();
            const foundStation = bandStations.find(station => Math.abs(station.frequency - currentFreq) <= epsilon);

            if (foundStation) {
                stopSearch(foundStation);
            }
        }, 45);
    }

    function stopSearch(station) {
        clearInterval(searchInterval);
        isSearching = false;
        tuningAudio.pause();

        if (station) {
            currentFreq = station.frequency;
            rebindRadioUI();
            playStationStream(station.url);
        }
    }

    function getActiveBandStations() {
        return stations.filter(s => {
            const f = parseFloat(s.frequency);
            if (currentBand === 'AM') {
                return f >= 530 && f <= 1700;
            } else {
                return f >= 88.0 && f <= 108.0;
            }
        });
    }

    function updateScaleLabels() {
        const labelsContainers = document.querySelectorAll('.tuner-labels');
        labelsContainers.forEach(container => {
            let html = '';
            if (currentBand === 'AM') {
                const amLabels = [530, 650, 770, 890, 1000, 1120, 1240, 1360, 1480, 1600, 1700];
                amLabels.forEach(val => {
                    html += `<span>${val}</span>`;
                });
            } else {
                const fmLabels = [88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108];
                fmLabels.forEach(val => {
                    html += `<span>${val}</span>`;
                });
            }
            container.innerHTML = html;
        });
        initScaleLabels();
    }
    window.plugtuner_tuner.updateScaleLabels = updateScaleLabels;

    function startVUNeedleAnimation() {
        if (vuAnimationId) {
            cancelAnimationFrame(vuAnimationId);
            vuAnimationId = null;
        }

        const activeCards = document.querySelectorAll('.floating-player-card');
        let hasVUTheme = false;
        activeCards.forEach(card => {
            card.classList.forEach(cls => {
                if (cls.startsWith('rtp-design-') && (cls.includes('vu-') || cls.includes('fm-am-'))) {
                    hasVUTheme = true;
                }
            });
        });

        if (!hasVUTheme) return;

        function draw() {
            let targetAngleL = -50;
            let targetAngleR = -50;

            if (isPlaying) {
                let rmsL = 0;
                let rmsR = 0;

                if (analyserLeft && analyserRight && !isCorsBypassed) {
                    const dataArrayL = new Uint8Array(analyserLeft.frequencyBinCount);
                    const dataArrayR = new Uint8Array(analyserRight.frequencyBinCount);
                    analyserLeft.getByteTimeDomainData(dataArrayL);
                    analyserRight.getByteTimeDomainData(dataArrayR);

                    let sumL = 0;
                    let sumR = 0;
                    for (let i = 0; i < dataArrayL.length; i++) {
                        const valL = (dataArrayL[i] - 128) / 128;
                        const valR = (dataArrayR[i] - 128) / 128;
                        sumL += valL * valL;
                        sumR += valR * valR;
                    }
                    rmsL = Math.sqrt(sumL / dataArrayL.length);
                    rmsR = Math.sqrt(sumR / dataArrayR.length);

                    targetAngleL = -50 + Math.min(1.0, rmsL * 2.2) * 78;
                    targetAngleR = -50 + Math.min(1.0, rmsR * 2.2) * 78;
                } else {
                    const time = Date.now() * 0.001;
                    const beat = Math.pow(Math.sin(time * 2.5) * 0.5 + 0.5, 3);
                    const subBeat = Math.sin(time * 6.3) * 0.2 + 0.2;
                    const noiseL = Math.random() * 0.25;
                    const noiseR = Math.random() * 0.25;

                    const ampL = Math.min(1.0, beat * 0.6 + subBeat * 0.2 + noiseL);
                    const ampR = Math.min(1.0, beat * 0.5 + subBeat * 0.3 + noiseR);

                    targetAngleL = -50 + ampL * 78;
                    targetAngleR = -50 + ampR * 78;
                }
            } else {
                const noiseL = Math.random() * 1.5;
                const noiseR = Math.random() * 1.5;
                targetAngleL = -50 + noiseL;
                targetAngleR = -50 + noiseR;
            }

            const speedRise = 0.25;
            const speedFall = 0.08;

            const diffL = targetAngleL - currentAngleLeft;
            currentAngleLeft += diffL * (diffL > 0 ? speedRise : speedFall);

            const diffR = targetAngleR - currentAngleRight;
            currentAngleRight += diffR * (diffR > 0 ? speedRise : speedFall);

            currentAngleLeft = Math.max(-50, Math.min(28, currentAngleLeft));
            currentAngleRight = Math.max(-50, Math.min(28, currentAngleRight));

            const needlesL = document.querySelectorAll('.vu-needle-left');
            const needlesR = document.querySelectorAll('.vu-needle-right');
            
            needlesL.forEach(needle => {
                needle.style.transform = `rotate(${currentAngleLeft}deg)`;
            });
            needlesR.forEach(needle => {
                needle.style.transform = `rotate(${currentAngleRight}deg)`;
            });

            vuAnimationId = requestAnimationFrame(draw);
        }

        vuAnimationId = requestAnimationFrame(draw);
    }
    window.plugtuner_tuner.startVUNeedleAnimation = startVUNeedleAnimation;

    let visualizerAnimationId = null;
    let visualizerHeights = Array(20).fill(10); // Current heights of the 20 bars in %

    function startVisualizerAnimation() {
        if (visualizerAnimationId) {
            cancelAnimationFrame(visualizerAnimationId);
            visualizerAnimationId = null;
        }

        const activeCards = document.querySelectorAll('.floating-player-card');
        let hasFreeTheme = false;
        activeCards.forEach(card => {
            if (card.classList.contains('rtp-design-free-light') || card.classList.contains('rtp-design-free-dark')) {
                hasFreeTheme = true;
            }
        });

        if (!hasFreeTheme) return;

        function updateVisualizer() {
            const bars = document.querySelectorAll('.floating-player-card .rtp-bar-inner');
            if (bars.length === 0) {
                visualizerAnimationId = requestAnimationFrame(updateVisualizer);
                return;
            }

            let freqs = Array(20).fill(0);

            if (isPlaying) {
                if (analyserLeft && !isCorsBypassed) {
                    // Real frequency analyzer data!
                    const dataArray = new Uint8Array(analyserLeft.frequencyBinCount);
                    analyserLeft.getByteFrequencyData(dataArray);

                    for (let i = 0; i < 20; i++) {
                        // Map 0-255 frequency amplitude to 10% - 100% height
                        const raw = dataArray[i] || 0;
                        freqs[i] = 10 + (raw / 255) * 90;
                    }
                } else {
                    // Procedural fallback animation (looks real & responds dynamically)
                    const time = Date.now() * 0.001;
                    for (let i = 0; i < 20; i++) {
                        const wave1 = Math.sin(time * 4.0 + i * 0.3) * 0.5 + 0.5;
                        const wave2 = Math.sin(time * 9.5 - i * 0.5) * 0.5 + 0.5;
                        const noise = Math.random() * 0.2;
                        
                        const bandScale = i < 4 ? 0.95 : (i < 12 ? 0.8 : 0.6);
                        const amp = Math.min(1.0, (wave1 * 0.6 + wave2 * 0.3 + noise) * bandScale);
                        freqs[i] = 10 + amp * 90;
                    }
                }
            } else {
                // Decay to baseline when paused
                for (let i = 0; i < 20; i++) {
                    freqs[i] = 10 + Math.random() * 1.5;
                }
            }

            // Smooth the changes using LERP
            for (let i = 0; i < 20; i++) {
                const diff = freqs[i] - visualizerHeights[i];
                const lerpFactor = diff > 0 ? 0.35 : 0.15; // Rise fast, fall slower
                visualizerHeights[i] += diff * lerpFactor;
                visualizerHeights[i] = Math.max(10, Math.min(100, visualizerHeights[i]));
            }

            // Apply heights to DOM bars
            bars.forEach((bar, index) => {
                if (index < 20) {
                    bar.style.height = `${visualizerHeights[index]}%`;
                }
            });

            visualizerAnimationId = requestAnimationFrame(updateVisualizer);
        }

        visualizerAnimationId = requestAnimationFrame(updateVisualizer);
    }
    window.plugtuner_tuner.startVisualizerAnimation = startVisualizerAnimation;

    // Refresh tuner UI
    function rebindRadioUI() {
        connectEqualizerFilters();

        const needles = document.querySelectorAll('.tuner-needle');
        const freqDisplays = document.querySelectorAll('.active-station-freq');
        const trackTitles = document.querySelectorAll('.track-title-name');
        const trackDescs = document.querySelectorAll('.track-artist-desc');
        const playerCoverImgs = document.querySelectorAll('.player-cover-img');
        const likeBtns = document.querySelectorAll('.rtp-like-btn');

        // Minimized Elements
        const minimizedCovers = document.querySelectorAll('.rtp-minimized-cover-img');
        const minimizedTitles = document.querySelectorAll('.rtp-minimized-title');
        const minimizedDescs = document.querySelectorAll('.rtp-minimized-desc');

        const bandStations = getActiveBandStations();
        const epsilon = currentBand === 'AM' ? 1.0 : 0.05;
        const currentStation = bandStations.find(s => Math.abs(s.frequency - currentFreq) < epsilon);
        const playingStation = bandStations.find(s => audioPlayer.src && !audioPlayer.paused && (audioPlayer.src === s.url || audioPlayer.src.includes(s.url)));

        freqDisplays.forEach(freqDisplay => {
            freqDisplay.innerText = currentBand === 'AM' ? currentFreq.toFixed(0) : currentFreq.toFixed(1);
        });

        const suffixes = document.querySelectorAll('.tuner-suffix');
        suffixes.forEach(suffix => {
            suffix.innerText = currentBand === 'AM' ? 'kHz' : 'MHz';
        });

        needles.forEach(needle => {
            const percentage = ((currentFreq - minFreq) / (maxFreq - minFreq)) * 100;
            needle.style.left = `${percentage}%`;
        });

        let activeStation = null;

        if (playingStation) {
            if (currentFreq !== playingStation.frequency && !isSearching) {
                currentFreq = playingStation.frequency;
                freqDisplays.forEach(freqDisplay => {
                    freqDisplay.innerText = currentBand === 'AM' ? currentFreq.toFixed(0) : currentFreq.toFixed(1);
                });
                needles.forEach(needle => {
                    const percentage = ((currentFreq - minFreq) / (maxFreq - minFreq)) * 100;
                    needle.style.left = `${percentage}%`;
                });
            }
            activeStation = playingStation;
        } else if (window.plugtuner_tuner.externalStationName && audioPlayer.src && !audioPlayer.paused) {
            // Playing global search station
            trackTitles.forEach(t => t.innerText = window.plugtuner_tuner.externalStationName);
            trackDescs.forEach(d => d.innerText = window.plugtuner_tuner.externalStationDesc || `${currentBand === 'AM' ? currentFreq.toFixed(0) : currentFreq.toFixed(1)} ${currentBand}`);
            playerCoverImgs.forEach(img => img.src = plugtuner_public_vars.default_image || (plugtuner_public_vars.plugin_url + "public/images/tuner-image.jpg"));
            
            minimizedTitles.forEach(t => t.innerText = window.plugtuner_tuner.externalStationName);
            minimizedDescs.forEach(d => d.innerText = window.plugtuner_tuner.externalStationDesc || `${currentBand === 'AM' ? currentFreq.toFixed(0) : currentFreq.toFixed(1)} ${currentBand}`);
            minimizedCovers.forEach(img => img.src = plugtuner_public_vars.default_image || (plugtuner_public_vars.plugin_url + "public/images/tuner-image.jpg"));
        } else {
            activeStation = currentStation;
        }

        if (activeStation) {
            trackTitles.forEach(t => t.innerText = activeStation.name);
            const freqStr = currentBand === 'AM' ? parseFloat(activeStation.frequency).toFixed(0) : parseFloat(activeStation.frequency).toFixed(1);
            let desc = `${freqStr} ${currentBand}`;
            if (activeStation.genre) {
                desc += ` • ${activeStation.genre}`;
            }
            if (activeStation.bitrate && activeStation.bitrate > 0) {
                desc += ` • ${activeStation.bitrate} kbps`;
            }
            trackDescs.forEach(d => d.innerText = desc);
            playerCoverImgs.forEach(img => {
                img.src = activeStation.image ? activeStation.image : (plugtuner_public_vars.default_image || (plugtuner_public_vars.plugin_url + "public/images/tuner-image.jpg"));
            });

            minimizedTitles.forEach(t => t.innerText = activeStation.name);
            minimizedDescs.forEach(d => d.innerText = desc);
            minimizedCovers.forEach(img => {
                img.src = activeStation.image ? activeStation.image : (plugtuner_public_vars.default_image || (plugtuner_public_vars.plugin_url + "public/images/tuner-image.jpg"));
            });

            // Sync Like & Dislike Button state and count
            const likedList = getLikedStations();
            const dislikedList = getDislikedStations();
            const isLiked = likedList.includes(activeStation.url);
            const isDisliked = dislikedList.includes(activeStation.url);

            const likesCount = activeStation.likes || 0;
            const dislikesCount = activeStation.dislikes || 0;

            likeBtns.forEach(likeBtn => {
                const countSpan = likeBtn.querySelector('.rtp-like-count');
                if (countSpan) {
                    countSpan.innerText = likesCount;
                }
                const icon = likeBtn.querySelector('i');
                if (icon) {
                    if (isLiked) {
                        likeBtn.classList.add('liked');
                        icon.className = 'fa-solid fa-thumbs-up';
                    } else {
                        likeBtn.classList.remove('liked');
                        icon.className = 'fa-regular fa-thumbs-up';
                    }
                }
            });

            const dislikeBtns = document.querySelectorAll('.rtp-dislike-btn');
            dislikeBtns.forEach(dislikeBtn => {
                const countSpan = dislikeBtn.querySelector('.rtp-dislike-count');
                if (countSpan) {
                    countSpan.innerText = dislikesCount;
                }
                const icon = dislikeBtn.querySelector('i');
                if (icon) {
                    if (isDisliked) {
                        dislikeBtn.classList.add('disliked');
                        icon.className = 'fa-solid fa-thumbs-down';
                    } else {
                        dislikeBtn.classList.remove('disliked');
                        icon.className = 'fa-regular fa-thumbs-down';
                    }
                }
            });
        } else if (!window.plugtuner_tuner.externalStationName || audioPlayer.paused) {
            trackTitles.forEach(t => t.innerText = "Noise / Tuning...");
            const freqStr = currentBand === 'AM' ? currentFreq.toFixed(0) : currentFreq.toFixed(1);
            trackDescs.forEach(d => d.innerText = `${freqStr} ${currentBand} • Searching...`);
            playerCoverImgs.forEach(img => img.src = plugtuner_public_vars.default_image || (plugtuner_public_vars.plugin_url + "public/images/tuner-image.jpg"));
            
            minimizedTitles.forEach(t => t.innerText = "Noise / Tuning...");
            minimizedDescs.forEach(d => d.innerText = `${freqStr} ${currentBand} • Searching...`);
            minimizedCovers.forEach(img => img.src = plugtuner_public_vars.default_image || (plugtuner_public_vars.plugin_url + "public/images/tuner-image.jpg"));

            likeBtns.forEach(likeBtn => {
                likeBtn.classList.remove('liked');
                const icon = likeBtn.querySelector('i');
                if (icon) {
                    icon.className = 'fa-regular fa-thumbs-up';
                }
                const countSpan = likeBtn.querySelector('.rtp-like-count');
                if (countSpan) {
                    countSpan.innerText = '0';
                }
            });

            const dislikeBtns = document.querySelectorAll('.rtp-dislike-btn');
            dislikeBtns.forEach(dislikeBtn => {
                dislikeBtn.classList.remove('disliked');
                const icon = dislikeBtn.querySelector('i');
                if (icon) {
                    icon.className = 'fa-regular fa-thumbs-down';
                }
                const countSpan = dislikeBtn.querySelector('.rtp-dislike-count');
                if (countSpan) {
                    countSpan.innerText = '0';
                }
            });
        }
    }
    window.plugtuner_tuner.rebindRadioUI = rebindRadioUI;

    /* -------------------------------------------------------------
     * Now Playing Metadata — Pro feature.
     * Live track-title reading is not part of this plugin, so these are
     * lightweight no-ops (no server requests).
     * ------------------------------------------------------------- */
    function startNowPlayingPolling(streamUrl) {
        currentPlayingUrl = streamUrl || '';
    }

    function stopNowPlayingPolling() {
        currentPlayingUrl = '';
        const nowPlayingEls = document.querySelectorAll('.player-now-playing');
        nowPlayingEls.forEach(nowPlayingEl => {
            nowPlayingEl.innerText = '';
            nowPlayingEl.style.display = 'none';
        });
    }

    function getLikedStations() {
        try {
            return JSON.parse(localStorage.getItem('plugtuner_liked_stations') || '[]');
        } catch (e) {
            return [];
        }
    }

    function getDislikedStations() {
        try {
            return JSON.parse(localStorage.getItem('plugtuner_disliked_stations') || '[]');
        } catch (e) {
            return [];
        }
    }

    function toggleLikeStation(url) {
        let likedList = getLikedStations();
        let dislikedList = getDislikedStations();
        const activeStation = stations.find(s => s.url === url);
        if (!activeStation) return;

        const isCurrentlyLiked = likedList.includes(url);
        const isCurrentlyDisliked = dislikedList.includes(url);

        if (isCurrentlyLiked) {
            likedList = likedList.filter(u => u !== url);
            localStorage.setItem('plugtuner_liked_stations', JSON.stringify(likedList));
            
            $.post(plugtuner_public_vars.ajax_url, {
                action: 'plugtuner_toggle_like',
                nonce: plugtuner_public_vars.nonce,
                stream_url: url,
                type: 'like',
                status: 'inactive'
            }, function(response) {
                if (response.success) {
                    activeStation.likes = response.data.likes;
                    activeStation.dislikes = response.data.dislikes;
                    rebindRadioUI();
                }
            });
        } else {
            likedList.push(url);
            localStorage.setItem('plugtuner_liked_stations', JSON.stringify(likedList));

            let hadDislike = false;
            if (isCurrentlyDisliked) {
                hadDislike = true;
                dislikedList = dislikedList.filter(u => u !== url);
                localStorage.setItem('plugtuner_disliked_stations', JSON.stringify(dislikedList));
            }

            $.post(plugtuner_public_vars.ajax_url, {
                action: 'plugtuner_toggle_like',
                nonce: plugtuner_public_vars.nonce,
                stream_url: url,
                type: 'like',
                status: 'active'
            }, function(response) {
                if (response.success) {
                    activeStation.likes = response.data.likes;
                    activeStation.dislikes = response.data.dislikes;
                    
                    if (hadDislike) {
                        $.post(plugtuner_public_vars.ajax_url, {
                            action: 'plugtuner_toggle_like',
                            nonce: plugtuner_public_vars.nonce,
                            stream_url: url,
                            type: 'dislike',
                            status: 'inactive'
                        }, function(subResp) {
                            if (subResp.success) {
                                activeStation.likes = subResp.data.likes;
                                activeStation.dislikes = subResp.data.dislikes;
                                rebindRadioUI();
                            }
                        });
                    } else {
                        rebindRadioUI();
                    }
                }
            });
        }
    }

    function toggleDislikeStation(url) {
        let likedList = getLikedStations();
        let dislikedList = getDislikedStations();
        const activeStation = stations.find(s => s.url === url);
        if (!activeStation) return;

        const isCurrentlyLiked = likedList.includes(url);
        const isCurrentlyDisliked = dislikedList.includes(url);

        if (isCurrentlyDisliked) {
            dislikedList = dislikedList.filter(u => u !== url);
            localStorage.setItem('plugtuner_disliked_stations', JSON.stringify(dislikedList));
            
            $.post(plugtuner_public_vars.ajax_url, {
                action: 'plugtuner_toggle_like',
                nonce: plugtuner_public_vars.nonce,
                stream_url: url,
                type: 'dislike',
                status: 'inactive'
            }, function(response) {
                if (response.success) {
                    activeStation.likes = response.data.likes;
                    activeStation.dislikes = response.data.dislikes;
                    rebindRadioUI();
                }
            });
        } else {
            dislikedList.push(url);
            localStorage.setItem('plugtuner_disliked_stations', JSON.stringify(dislikedList));

            let hadLike = false;
            if (isCurrentlyLiked) {
                hadLike = true;
                likedList = likedList.filter(u => u !== url);
                localStorage.setItem('plugtuner_liked_stations', JSON.stringify(likedList));
            }

            $.post(plugtuner_public_vars.ajax_url, {
                action: 'plugtuner_toggle_like',
                nonce: plugtuner_public_vars.nonce,
                stream_url: url,
                type: 'dislike',
                status: 'active'
            }, function(response) {
                if (response.success) {
                    activeStation.likes = response.data.likes;
                    activeStation.dislikes = response.data.dislikes;
                    
                    if (hadLike) {
                        $.post(plugtuner_public_vars.ajax_url, {
                            action: 'plugtuner_toggle_like',
                            nonce: plugtuner_public_vars.nonce,
                            stream_url: url,
                            type: 'like',
                            status: 'inactive'
                        }, function(subResp) {
                            if (subResp.success) {
                                activeStation.likes = subResp.data.likes;
                                activeStation.dislikes = subResp.data.dislikes;
                                rebindRadioUI();
                            }
                        });
                    } else {
                        rebindRadioUI();
                    }
                }
            });
        }
    }

    /* -------------------------------------------------------------
     * Session Storage State Saving (Autoplay Resume)
     * ------------------------------------------------------------- */
    function saveSessionState() {
        sessionStorage.setItem('plugtuner_current_freq', currentFreq);
        sessionStorage.setItem('plugtuner_is_playing', isPlaying ? '1' : '0');
        sessionStorage.setItem('plugtuner_volume', audioPlayer.volume);
        sessionStorage.setItem('plugtuner_current_band', currentBand);
        if (audioPlayer.src) {
            sessionStorage.setItem('plugtuner_current_stream', audioPlayer.src);
        } else {
            sessionStorage.removeItem('plugtuner_current_stream');
        }
    }

    function loadSessionState() {
        let savedFreq = sessionStorage.getItem('plugtuner_current_freq');
        let savedPlaying = sessionStorage.getItem('plugtuner_is_playing');
        const savedVolume = sessionStorage.getItem('plugtuner_volume');
        let savedStream = sessionStorage.getItem('plugtuner_current_stream');
        const savedMinimized = sessionStorage.getItem('plugtuner_player_minimized');
        let savedBand = sessionStorage.getItem('plugtuner_current_band') || 'FM';

        // The AM band only exists on designs that render the FM/AM toggle.
        // If the admin switched to an FM-only design while the visitor's session
        // still remembers AM, discard the stale AM state and start clean on FM.
        const supportsAM = document.querySelector('.floating-player-card .band-btn') !== null;
        if (savedBand === 'AM' && !supportsAM) {
            savedBand = 'FM';
            savedFreq = null;
            savedStream = null;
            savedPlaying = '0';
            sessionStorage.setItem('plugtuner_current_band', 'FM');
            sessionStorage.removeItem('plugtuner_current_freq');
            sessionStorage.removeItem('plugtuner_current_stream');
            sessionStorage.setItem('plugtuner_is_playing', '0');
        }

        if (savedMinimized === '1') {
            document.querySelectorAll('.floating-player-card.rtp-floating-player').forEach(c => {
                c.classList.add('rtp-minimized');
            });
        }

        currentBand = savedBand;
        document.querySelectorAll('.band-btn').forEach(b => {
            b.classList.toggle('active', b.getAttribute('data-band') === currentBand);
        });

        if (currentBand === 'AM') {
            minFreq = 530;
            maxFreq = 1700;
            searchSpeed = 10;
        } else {
            minFreq = 88.0;
            maxFreq = 108.0;
            searchSpeed = 0.1;
        }

        // Ignore a remembered frequency that falls outside the active band's scale
        if (savedFreq !== null && (isNaN(parseFloat(savedFreq)) || parseFloat(savedFreq) < minFreq || parseFloat(savedFreq) > maxFreq)) {
            savedFreq = null;
        }

        if (savedFreq) {
            currentFreq = parseFloat(savedFreq);
        } else {
            const bandStations = getActiveBandStations();
            if (bandStations.length > 0) {
                currentFreq = parseFloat(bandStations[0].frequency);
            } else {
                currentFreq = minFreq;
            }
        }

        if (savedVolume !== null) {
            audioPlayer.volume = parseFloat(savedVolume);
            tuningAudio.volume = parseFloat(savedVolume);
            const volumeSliders = document.querySelectorAll('.volume-slider');
            volumeSliders.forEach(volumeSlider => {
                volumeSlider.value = Math.round(audioPlayer.volume * 100);
                const volumeFill = volumeSlider.closest('.volume-slider-wrapper')?.querySelector('.volume-slider-fill');
                if (volumeFill) {
                    volumeFill.style.width = `${Math.round(audioPlayer.volume * 100)}%`;
                }
            });
        }

        updateScaleLabels();
        rebindRadioUI();

        // Autoplay if it was playing and autoplay is permitted
        if (savedPlaying === '1' && savedStream && plugtuner_public_vars.enable_autoplay === '1') {
            playStationStream(savedStream);
        }
    }

    /* -------------------------------------------------------------
     * Initialize Event Handlers
     * ------------------------------------------------------------- */
    function initRadioEvents() {
        const cards = document.querySelectorAll('.floating-player-card');
        
        // Play / Pause click handler
        const handlePlayPause = (e) => {
            if (e) e.preventDefault();
            if (isSearching) return;

            if (!audioCtx) {
                initAudioContext();
            }
            if (audioCtx && audioCtx.state === "suspended") {
                audioCtx.resume();
            }

            if (isPlaying) {
                audioPlayer.pause();
                setPlayState(false);
                saveSessionState();
            } else {
                if (audioPlayer.src) {
                    isPlaying = true;
                    audioPlayer.play()
                        .then(() => {
                            setPlayState(true);
                            saveSessionState();
                        })
                        .catch(err => {
                            console.error('Audio play failed:', err);
                            if (!audioPlayer.isDiscarded) {
                                setPlayState(false);
                            }
                        });
                } else {
                    const bandStations = getActiveBandStations();
                    const currentStation = bandStations.find(s => Math.abs(s.frequency - currentFreq) < 1.0) || bandStations[0];
                    if (currentStation) {
                        currentFreq = currentStation.frequency;
                        rebindRadioUI();
                        playStationStream(currentStation.url);
                    }
                }
            }
        };

        cards.forEach((card) => {
            const playBtn = card.querySelector('.player-play-btn');
            const btnScaleLeft = card.querySelector('.btn-scale-left');
            const btnScaleRight = card.querySelector('.btn-scale-right');
            const scaleContainer = card.querySelector('.tuner-scale-container');
            const needle = card.querySelector('.tuner-needle');
            const likeBtn = card.querySelector('.rtp-like-btn');
            const volumeSlider = card.querySelector('.volume-slider');
            const muteBtn = card.querySelector('.volume-mute-btn');
            const tracks = card.querySelectorAll('.equalizer-track, .rtp-vu-equalizer-track');

            const minimizeBtn = card.querySelector('.rtp-minimize-btn');
            const minimizedPlayBtn = card.querySelector('.rtp-minimized-play-btn');
            const minimizedExpandBtn = card.querySelector('.rtp-minimized-expand-btn');
            const minimizedInfo = card.querySelector('.rtp-minimized-info');

            if (minimizeBtn) {
                const newMinimize = minimizeBtn.cloneNode(true);
                minimizeBtn.replaceWith(newMinimize);
                newMinimize.addEventListener('click', (e) => {
                    e.preventDefault();
                    document.querySelectorAll('.floating-player-card.rtp-floating-player').forEach(c => {
                        c.classList.add('rtp-minimized');
                    });
                    sessionStorage.setItem('plugtuner_player_minimized', '1');
                });
            }

            if (minimizedExpandBtn) {
                const newExpand = minimizedExpandBtn.cloneNode(true);
                minimizedExpandBtn.replaceWith(newExpand);
                newExpand.addEventListener('click', (e) => {
                    e.preventDefault();
                    document.querySelectorAll('.floating-player-card.rtp-floating-player').forEach(c => {
                        c.classList.remove('rtp-minimized');
                    });
                    sessionStorage.setItem('plugtuner_player_minimized', '0');
                });
            }

            if (minimizedInfo) {
                const newInfo = minimizedInfo.cloneNode(true);
                minimizedInfo.replaceWith(newInfo);
                newInfo.addEventListener('click', (e) => {
                    e.preventDefault();
                    document.querySelectorAll('.floating-player-card.rtp-floating-player').forEach(c => {
                        c.classList.remove('rtp-minimized');
                    });
                    sessionStorage.setItem('plugtuner_player_minimized', '0');
                });
            }

            if (minimizedPlayBtn) {
                const newMinPlay = minimizedPlayBtn.cloneNode(true);
                minimizedPlayBtn.replaceWith(newMinPlay);
                newMinPlay.addEventListener('click', handlePlayPause);
            }

            if (playBtn) {
                const newPlayBtn = playBtn.cloneNode(true);
                playBtn.replaceWith(newPlayBtn);
                newPlayBtn.addEventListener('click', handlePlayPause);
            }

            if (btnScaleLeft) {
                const newLeft = btnScaleLeft.cloneNode(true);
                btnScaleLeft.replaceWith(newLeft);
                newLeft.addEventListener('click', (e) => {
                    e.preventDefault();
                    startSearch(-1);
                });
            }

            if (btnScaleRight) {
                const newRight = btnScaleRight.cloneNode(true);
                btnScaleRight.replaceWith(newRight);
                newRight.addEventListener('click', (e) => {
                    e.preventDefault();
                    startSearch(1);
                });
            }

            // Band Toggles
            const bandBtns = card.querySelectorAll('.band-btn');
            bandBtns.forEach(btn => {
                const newBtn = btn.cloneNode(true);
                btn.replaceWith(newBtn);
                newBtn.addEventListener('click', (e) => {
                    e.preventDefault();
                    const selectedBand = newBtn.getAttribute('data-band');
                    if (selectedBand === currentBand) return;

                    if (isSearching) {
                        stopSearch(null);
                    }

                    currentBand = selectedBand;

                    // Sync button active classes on all cards
                    document.querySelectorAll('.band-btn').forEach(b => {
                        b.classList.toggle('active', b.getAttribute('data-band') === currentBand);
                    });

                    if (currentBand === 'AM') {
                        minFreq = 530;
                        maxFreq = 1700;
                        searchSpeed = 10;
                    } else {
                        minFreq = 88.0;
                        maxFreq = 108.0;
                        searchSpeed = 0.1;
                    }

                    audioPlayer.pause();
                    audioPlayer.removeAttribute('src');
                    audioPlayer.load();
                    setPlayState(false);

                    const bandStations = getActiveBandStations();
                    if (bandStations.length > 0) {
                        currentFreq = parseFloat(bandStations[0].frequency);
                    } else {
                        currentFreq = minFreq;
                    }

                    updateScaleLabels();
                    rebindRadioUI();
                    saveSessionState();
                });
            });

            // VU / EQ Toggle Link
            const toggleLink = card.querySelector('.rtp-vu-eq-toggle-link');
            const vuContainer = card.querySelector('.vu-meters-container');
            const eqSection = card.querySelector('.player-equalizer-section, .rtp-vu-equalizer-section');
            const controlsSection = card.querySelector('.player-controls-section');

            const setTunerMode = (mode) => {
                const allControlsSections = document.querySelectorAll('.player-controls-section');
                const allVuContainers = document.querySelectorAll('.vu-meters-container');
                const allEqSections = document.querySelectorAll('.player-equalizer-section, .rtp-vu-equalizer-section');
                const allToggleLinks = document.querySelectorAll('.rtp-vu-eq-toggle-link');

                if (mode === 'eq') {
                    allVuContainers.forEach(v => v.style.display = 'none');
                    allEqSections.forEach(eq => eq.style.display = 'flex');
                    allToggleLinks.forEach(t => t.innerText = 'VU INDICATORS');
                    allControlsSections.forEach(c => c.classList.remove('rtp-vu-active'));
                } else {
                    allVuContainers.forEach(v => v.style.display = 'flex');
                    allEqSections.forEach(eq => eq.style.display = 'none');
                    allToggleLinks.forEach(t => t.innerText = 'EQUALIZER');
                    allControlsSections.forEach(c => c.classList.add('rtp-vu-active'));
                }
            };

            if (toggleLink) {
                const newToggleLink = toggleLink.cloneNode(true);
                toggleLink.replaceWith(newToggleLink);
                
                const isVuVisible = vuContainer && window.getComputedStyle(vuContainer).display !== 'none';
                setTunerMode(isVuVisible ? 'vu' : 'eq');

                newToggleLink.addEventListener('click', (e) => {
                    e.preventDefault();
                    const isVuActive = vuContainer && vuContainer.style.display !== 'none';
                    setTunerMode(isVuActive ? 'eq' : 'vu');
                });
            }

            if (likeBtn) {
                const newLike = likeBtn.cloneNode(true);
                likeBtn.replaceWith(newLike);
                newLike.addEventListener('click', function(e) {
                    e.preventDefault();
                    const bandStations = getActiveBandStations();
                    const epsilon = currentBand === 'AM' ? 1.0 : 0.05;
                    const activeStation = bandStations.find(s => Math.abs(s.frequency - currentFreq) < epsilon);
                    if (activeStation) {
                        toggleLikeStation(activeStation.url);
                    } else if (audioPlayer.src) {
                        toggleLikeStation(audioPlayer.src);
                    }
                });
            }

            const dislikeBtn = card.querySelector('.rtp-dislike-btn');
            if (dislikeBtn) {
                const newDislike = dislikeBtn.cloneNode(true);
                dislikeBtn.replaceWith(newDislike);
                newDislike.addEventListener('click', function(e) {
                    e.preventDefault();
                    const bandStations = getActiveBandStations();
                    const epsilon = currentBand === 'AM' ? 1.0 : 0.05;
                    const activeStation = bandStations.find(s => Math.abs(s.frequency - currentFreq) < epsilon);
                    if (activeStation) {
                        toggleDislikeStation(activeStation.url);
                    } else if (audioPlayer.src) {
                        toggleDislikeStation(audioPlayer.src);
                    }
                });
            }

            if (volumeSlider) {
                const newSlider = volumeSlider.cloneNode(true);
                volumeSlider.replaceWith(newSlider);

                newSlider.addEventListener('input', (e) => {
                    const val = e.target.value;
                    audioPlayer.volume = val / 100;
                    tuningAudio.volume = val / 100;

                    // Sync all volume sliders on page
                    const allSliders = document.querySelectorAll('.volume-slider');
                    allSliders.forEach(slider => {
                        slider.value = val;
                        const fill = slider.closest('.volume-slider-wrapper')?.querySelector('.volume-slider-fill');
                        if (fill) fill.style.width = `${val}%`;
                    });

                    // Sync mute buttons
                    const allMuteBtns = document.querySelectorAll('.volume-mute-btn');
                    allMuteBtns.forEach(mBtn => {
                        let iconClass = 'fa-solid fa-volume-high';
                        if (val == 0) iconClass = 'fa-solid fa-volume-xmark';
                        else if (val < 40) iconClass = 'fa-solid fa-volume-low';
                        mBtn.innerHTML = '<i class="' + iconClass + '"></i>';
                    });

                    saveSessionState();
                });

                if (muteBtn) {
                    const clonedMuteBtn = muteBtn.cloneNode(true);
                    muteBtn.replaceWith(clonedMuteBtn);
                    clonedMuteBtn.addEventListener('click', (e) => {
                        e.preventDefault();
                        const currentVal = parseFloat(newSlider.value);
                        if (currentVal > 0) {
                            card._preMuteVolume = currentVal;
                            const allSliders = document.querySelectorAll('.volume-slider');
                            allSliders.forEach(s => {
                                s.value = 0;
                                s.dispatchEvent(new Event('input'));
                            });
                        } else {
                            const restoreVal = card._preMuteVolume || 80;
                            const allSliders = document.querySelectorAll('.volume-slider');
                            allSliders.forEach(s => {
                                s.value = restoreVal;
                                s.dispatchEvent(new Event('input'));
                            });
                        }
                    });
                }
            }

            if (scaleContainer && needle) {
                let isDragging = false;

                const updateFreqFromCoordinate = (clientX) => {
                    const rect = scaleContainer.getBoundingClientRect();
                    let relativeX = (clientX - rect.left) / rect.width;
                    relativeX = Math.max(0, Math.min(1, relativeX));

                    currentFreq = minFreq + relativeX * (maxFreq - minFreq);
                    rebindRadioUI();

                    if (tuningAudio.paused) {
                        audioPlayer.pause();
                        audioPlayer.removeAttribute('src');
                        audioPlayer.load();
                        setPlayState(false);
                        tuningAudio.volume = audioPlayer.volume;
                        tuningAudio.play().catch(function() { /* autoplay policy may block the effect */ });
                    }
                };

                const onStartDrag = (e) => {
                    isDragging = true;
                    document.querySelectorAll('.tuner-needle').forEach(n => n.classList.add('dragging'));

                    const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                    updateFreqFromCoordinate(clientX);

                    if (!e.touches) e.preventDefault();

                    window.addEventListener('mousemove', onDrag);
                    window.addEventListener('mouseup', onStopDrag);
                    window.addEventListener('touchmove', onDrag, { passive: true });
                    window.addEventListener('touchend', onStopDrag);
                };

                const onDrag = (e) => {
                    if (!isDragging) return;
                    const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                    updateFreqFromCoordinate(clientX);
                };

                const onStopDrag = () => {
                    if (!isDragging) return;
                    isDragging = false;
                    document.querySelectorAll('.tuner-needle').forEach(n => n.classList.remove('dragging'));
                    tuningAudio.pause();

                    window.removeEventListener('mousemove', onDrag);
                    window.removeEventListener('mouseup', onStopDrag);
                    window.removeEventListener('touchmove', onDrag);
                    window.removeEventListener('touchend', onStopDrag);

                    // Snap to nearest frequency in active band
                    const bandStations = getActiveBandStations();
                    if (bandStations.length > 0) {
                        let closest = bandStations[0];
                        let minDiff = Math.abs(bandStations[0].frequency - currentFreq);

                        for (let i = 1; i < bandStations.length; i++) {
                            const diff = Math.abs(bandStations[i].frequency - currentFreq);
                            if (diff < minDiff) {
                                minDiff = diff;
                                closest = bandStations[i];
                            }
                        }

                        currentFreq = closest.frequency;
                        rebindRadioUI();
                        playStationStream(closest.url);
                    }
                };

                const newScaleContainer = scaleContainer.cloneNode(true);
                scaleContainer.replaceWith(newScaleContainer);
                newScaleContainer.addEventListener('mousedown', onStartDrag);
                newScaleContainer.addEventListener('touchstart', onStartDrag, { passive: true });
            }

            if (tracks.length > 0) {
                tracks.forEach((track, index) => {
                    let handle = track.querySelector('.equalizer-handle, .rtp-vu-equalizer-handle');
                    if (!handle) return;

                    const newHandle = handle.cloneNode(true);
                    handle.replaceWith(newHandle);
                    handle = newHandle;

                    let isDragging = false;

                    const onStartDrag = (e) => {
                        isDragging = true;
                        const allHandlesForIndex = document.querySelectorAll(
                            `.equalizer-bar:nth-child(${index + 1}) .equalizer-handle, ` +
                            `.rtp-vu-equalizer-bar:nth-child(${index + 1}) .rtp-vu-equalizer-handle`
                        );
                        allHandlesForIndex.forEach(h => {
                            h.classList.add('dragging');
                            h.style.cursor = 'grabbing';
                        });

                        const clientY = e.touches ? e.touches[0].clientY : e.clientY;
                        updatePosition(clientY);

                        if (e.cancelable) e.preventDefault();

                        document.addEventListener('mousemove', onDrag);
                        document.addEventListener('mouseup', onStopDrag);
                        document.addEventListener('touchmove', onDrag, { passive: false });
                        document.addEventListener('touchend', onStopDrag);
                    };

                    const updatePosition = (clientY) => {
                        const trackRect = track.getBoundingClientRect();
                        let relativeY = trackRect.bottom - clientY;
                        let percentage = (relativeY / trackRect.height) * 100;

                        percentage = Math.max(10, Math.min(90, percentage));

                        const allHandlesForIndex = document.querySelectorAll(
                            `.equalizer-bar:nth-child(${index + 1}) .equalizer-handle, ` +
                            `.rtp-vu-equalizer-bar:nth-child(${index + 1}) .rtp-vu-equalizer-handle`
                        );
                        allHandlesForIndex.forEach(h => {
                            h.style.bottom = `${percentage}%`;
                        });

                        customHeights[index] = percentage;

                        if (audioCtx && eqFilters[index]) {
                            eqFilters[index].gain.value = (percentage - 50) * (12 / 40);
                        }
                    };

                    const onDrag = (e) => {
                        if (!isDragging) return;
                        const clientY = e.touches ? e.touches[0].clientY : e.clientY;
                        updatePosition(clientY);
                        if (e.cancelable) e.preventDefault();
                    };

                    const onStopDrag = () => {
                        if (!isDragging) return;
                        isDragging = false;

                        const allHandlesForIndex = document.querySelectorAll(
                            `.equalizer-bar:nth-child(${index + 1}) .equalizer-handle, ` +
                            `.rtp-vu-equalizer-bar:nth-child(${index + 1}) .rtp-vu-equalizer-handle`
                        );
                        allHandlesForIndex.forEach(h => {
                            h.classList.remove('dragging');
                            h.style.cursor = '';
                        });

                        document.removeEventListener('mousemove', onDrag);
                        document.removeEventListener('mouseup', onStopDrag);
                        document.removeEventListener('touchmove', onDrag);
                        document.removeEventListener('touchend', onStopDrag);
                    };

                    handle.addEventListener('mousedown', onStartDrag);
                    handle.addEventListener('touchstart', onStartDrag, { passive: false });
                });
            }
        });
    }
    function updateStationsArray(newStations) {
        stations = newStations;
        rebindRadioUI();
    }
    window.plugtuner_tuner.updateStationsArray = updateStationsArray;

    window.plugtuner_tuner.initRadioEvents = initRadioEvents;

    // Robust entry point that does not fail if another script crashes the jQuery ready queue
    function initTunerDOM() {
        removeDuplicatePlayers();

        const $cards = $('.floating-player-card');
        if ($cards.length && !window.plugtuner_tuner_initialized_dom) {
            window.plugtuner_tuner_initialized_dom = true;
            
            $cards.each(function() {
                const $c = $(this);
                if ($c.hasClass('rtp-floating-player')) {
                    $c.addClass('rtp-initialized');
                    $c.appendTo('body');
                }
            });

            stations = plugtuner_public_vars.stations || [];
            initScaleLabels();
            initRadioEvents();
            loadSessionState();
            startVUNeedleAnimation();
            startVisualizerAnimation();
        }
    }

    // Try to initialize immediately (if script is in footer, DOM is ready)
    initTunerDOM();

    // Fallback 1: Native DOMContentLoaded event
    window.addEventListener('DOMContentLoaded', initTunerDOM);

    // Fallback 2: jQuery Document Ready
    $(document).ready(initTunerDOM);

})(jQuery);
