Internet Roadtrip Permanent Radios

Overrides Internet Roadtrip radio with your favorite radio

// ==UserScript==
// @name        Internet Roadtrip Permanent Radios
// @namespace   http://tampermonkey.net/
// @version     1.4
// @description Overrides Internet Roadtrip radio with your favorite radio
// @author      TotallyNotSamm
// @license     MIT
// @match       https://neal.fun/internet-roadtrip/
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @connect     azura.wbor.org
// @connect     playlists.wbor.org
// @connect     public.radio.co
// @connect     www.radio-browser.info
// @connect     de1.api.radio-browser.info
// @run-at      document-start
// @require     https://cdn.jsdelivr.net/npm/[email protected]
// @icon        https://i.redd.it/0xszn1428p5f1.png
// ==/UserScript==

(async () => {
    if (!IRF.isInternetRoadtrip) return;

    // Get the radio DOM element using IRF
    const radioBody = await IRF.dom.radio;

    let isPopupOpen = false;
    let infoButton;
    let tooltipSpan;
    let infoPopup;

    let customRadios = await GM_getValue("customRadios", []);
    const radios = [
        { name: "WBOR 91.1 FM", url: "https://listen.wbor.org/" },
        { name: "Folk'd Up Radio", url: "https://s4.radio.co/s129fcc067/listen" },
        { name: "CBFM Radio", url: "https://s4.radio.co/s6f58ddb4f/listen" },
        { name: "WMUA 91.1 FM", url: "https://usa5.fastcast4u.com/proxy/qernhlca?mp=/1" },
        { name: "WMUAx", url: "https://usa5.fastcast4u.com/proxy/qernhlca?mp=/2" },
        { name: "The Wave 100.9 FM", url: "https://mbsradio-ais.leanstream.co/CKTOFM-MP3" },
        { name: "ICI Musique Montréal", url: "https://rcavliveaudio.akamaized.net/hls/live/2006979/M-7QMTL0_MTL/master.m3u8", format: "hls" },
        { name: "ICI Musique Classique", url: "https://rcavliveaudio.akamaized.net/hls/live/2006977/M-2QMUCL_CLASSIQUE/master.m3u8", format: "hls" },
        { name: "CBC Radio One", url: "https://cbcradiolive.akamaized.net/hls/live/2040990/ES_R1ASY/adaptive_192/chunklist_ao.m3u8", format: "hls" }
    ];
    const allRadios = [...radios, ...customRadios];

    let selectedStationName = await GM_getValue("lastSelectedStation", allRadios[0].name);
    let selectedStation = allRadios.find(r => r.name === selectedStationName) || allRadios[0];
    let isOverrideEnabled = await GM_getValue("isOverrideEnabled", false);

    const originalUpdateData = (await IRF.vdom.container).methods.updateData;

    (await IRF.vdom.container).state.updateData = new Proxy(originalUpdateData, {
        apply: (target, thisArg, args) => {
            if (!isOverrideEnabled) {
                const currentStation = args[0].station?.name;
                const alreadySet = currentStation === selectedStation.name;

                if (!alreadySet) {
                    args[0].station = {
                        name: selectedStation.name,
                        url: selectedStation.url,
                        format: selectedStation.format || "mp3",
                        distance: 0
                    };
                }
            }

            IRF.vdom.radio.then(radio => {
                // Add error handling
                if (!radio.state._errorHandler) {
                    radio.state._errorHandler = true;

                    // Use IRF state for error handling
                    const handleStreamError = () => {
                        radio.state.stationInfo = "Connection Error - Reconnecting...";
                        setTimeout(() => {
                            console.log('Attempting to reconnect...');
                            radio.state.stationInfo = "TUNE IN";
                            setTimeout(() => {
                                radio.state.stationInfo = "PLAYING";
                            }, 1000);
                        }, 3000);
                    };

                    radio.state._handleStreamError = handleStreamError;
                }

                const currentStation = radio.state.stationName;
                if (radio.state.isPoweredOn && nowPlayingInfo[currentStation]) {
                    radio.state.stationInfo = nowPlayingInfo[currentStation].nowPlaying;
                } else {
                    radio.state.stationInfo = radio.state.isPoweredOn ? "PLAYING" : "TUNE IN";
                }
            });

            return Reflect.apply(target, thisArg, args);
        }
    });

    // Add info button and popup
    if (!radioBody) {
        console.warn("[Radio Info] Radio DOM element not found. The info button won't be displayed.");
        return;
    }

    // Find a more specific element to position relative, avoiding layout issues
    const radioContainer = radioBody.querySelector('.radio-body') || radioBody.querySelector('.station-name') || radioBody;
    radioContainer.style.position = "relative";

    // Create info button
    infoButton = document.createElement("button");
    infoButton.textContent = "i";
    infoButton.setAttribute("aria-label", "Show Radio Info");
    Object.assign(infoButton.style, {
        position: "absolute",
        top: "8px",
        left: "8px",
        width: "20px",
        height: "20px",
        borderRadius: "50%",
        border: "none",
        background: "transparent",
        color: "white",
        fontWeight: "bold",
        fontFamily: "inherit",
        cursor: "pointer",
        padding: "0",
        lineHeight: "18px",
        textAlign: "center",
        userSelect: "none",
        zIndex: "9999",
    });
    radioContainer.appendChild(infoButton);

    // Create tooltip
    tooltipSpan = document.createElement("div");
    tooltipSpan.textContent = "Show more info";
    Object.assign(tooltipSpan.style, {
        position: "fixed",
        backgroundColor: "rgba(0, 0, 0, 0.75)",
        color: "#fff",
        padding: "4px 8px",
        borderRadius: "4px",
        fontSize: "12px",
        fontFamily: "inherit",
        opacity: "0",
        visibility: "hidden",
        transition: "opacity 0.2s ease",
        whiteSpace: "nowrap",
        zIndex: "9998",
    });
    document.body.appendChild(tooltipSpan);

    // Create info popup
    infoPopup = document.createElement("div");
    const refStyles = getComputedStyle(radioBody.querySelector(".station-name"));
    Object.assign(infoPopup.style, {
        position: "fixed",
        backgroundColor: "rgba(0, 0, 0, 0.75)",
        color: "#fff",
        padding: "8px 12px",
        borderRadius: "6px",
        fontFamily: refStyles.fontFamily,
        fontWeight: "normal",
        fontSize: "14px",
        maxWidth: "240px",
        boxShadow: "0 2px 8px rgba(0,0,0,0.8)",
        opacity: "0",
        visibility: "hidden",
        transition: "opacity 0.25s ease",
        userSelect: "none",
        zIndex: "9998",
    });
    document.body.appendChild(infoPopup);

    // State for now playing info
    let nowPlayingInfo = {
        'WBOR 91.1 FM': {
            nowPlaying: 'Unknown Track – Unknown Artist'
        },
        'Folk\'d Up Radio': {
            nowPlaying: 'Unknown Track – Unknown Artist'
        },
        'CBFM Radio': {
            nowPlaying: 'Unknown Track – Unknown Artist'
        }
    };

    // Add station change tracking
    let currentStation = '';
    let liveShowName = null;
    let liveDjName = null;

    // Fetch WBOR now playing info
    async function fetchWBORInfo() {
        try {
            const response = await new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: "https://azura.wbor.org/api/nowplaying/1",
                    onload: resolve,
                    onerror: () => resolve({ responseText: '{}' })
                });
            });

            // Parse song info
            try {
                const songData = JSON.parse(response.responseText);
                const song = songData[0]?.now_playing?.song || {};
                const artist = song.artist || "Unknown Artist";
                const title = song.title || "Unknown Title";
                nowPlayingInfo['WBOR 91.1 FM'].nowPlaying = `${title} – ${artist}`;
            } catch (e) {
                console.error("[WBOR] Song info parse error:", e);
            }
        } catch (e) {
            console.error("[WBOR] Fetch error:", e);
        }
    }

    // Fetch WBOR live show info
    async function fetchLiveShowInfo() {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: "https://playlists.wbor.org/WBOR/",
                onload: function (response) {
                    try {
                        const parser = new DOMParser();
                        const doc = parser.parseFromString(response.responseText, "text/html");

                        const showTitleEl = doc.querySelector("h3.show-title a");
                        liveShowName = showTitleEl ? showTitleEl.textContent.trim() : null;

                        const djNameEl = doc.querySelector("p.dj-name a");
                        liveDjName = djNameEl ? djNameEl.textContent.trim() : null;

                    } catch (e) {
                        console.error("[WBOR] Failed to parse live show info HTML:", e);
                        liveShowName = null;
                        liveDjName = null;
                    } finally {
                        resolve();
                    }
                },
                onerror: function (e) {
                    console.error("[WBOR] GM_xmlhttpRequest failed for live show info:", e);
                    liveShowName = null;
                    liveDjName = null;
                    resolve();
                }
            });
        });
    }

    // Fetch Folk'd Up Radio now playing info
    async function fetchFolkdUpInfo() {
        try {
            const response = await new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: "https://public.radio.co/api/v2/s129fcc067/track/current",
                    onload: resolve,
                    onerror: () => resolve({ responseText: '{}' })
                });
            });

            const data = JSON.parse(response.responseText);
            nowPlayingInfo['Folk\'d Up Radio'].nowPlaying = data.data?.title || "Unknown Title";
        } catch (e) {
            console.error("[Folk'd Up] Fetch error:", e);
        }
    }

    // Fetch CBFM Radio now playing info
    async function fetchCBFMInfo() {
        try {
            const response = await new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: "https://public.radio.co/api/v2/s6f58ddb4f/track/current",
                    onload: resolve,
                    onerror: () => resolve({ responseText: '{}' })
                });
            });

            const data = JSON.parse(response.responseText);
            nowPlayingInfo['CBFM Radio'].nowPlaying = data.data?.title || "Unknown Title";
        } catch (e) {
            console.error("[CBFM] Fetch error:", e);
        }
    }

    // Update all info
    async function updateAllInfo() {
        await Promise.all([fetchWBORInfo(), fetchLiveShowInfo(), fetchFolkdUpInfo(), fetchCBFMInfo()]);
    }

    // === RADIO BROWSER API FUNCTIONS ===
    async function searchRadioBrowser(query, limit = 20) {
        try {
            const response = await new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: `https://de1.api.radio-browser.info/json/stations/search?name=${encodeURIComponent(query)}&limit=${limit}&hidebroken=true`,
                    onload: resolve,
                    onerror: () => resolve({ responseText: '[]' })
                });
            });

            const stations = JSON.parse(response.responseText);
            return stations.map(station => ({
                name: station.name,
                url: station.url_resolved || station.url,
                format: station.codec || "mp3",
                country: station.country,
                language: station.language,
                tags: station.tags,
                favicon: station.favicon,
                votes: station.votes,
                bitrate: station.bitrate
            }));
        } catch (e) {
            console.error("[Radio Browser] Search error:", e);
            return [];
        }
    }

    async function getPopularStations(limit = 10) {
        try {
            const response = await new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: `https://de1.api.radio-browser.info/json/stations/topvote?limit=${limit}&hidebroken=true`,
                    onload: resolve,
                    onerror: () => resolve({ responseText: '[]' })
                });
            });

            const stations = JSON.parse(response.responseText);
            return stations.map(station => ({
                name: station.name,
                url: station.url_resolved || station.url,
                format: station.codec || "mp3",
                country: station.country,
                language: station.language,
                tags: station.tags,
                favicon: station.favicon,
                votes: station.votes,
                bitrate: station.bitrate
            }));
        } catch (e) {
            console.error("[Radio Browser] Popular stations error:", e);
            return [];
        }
    }

    async function getRandomStation() {
        try {
            console.log("[Random Station] Fetching popular stations to pick random one...");

            const response = await new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: `https://de1.api.radio-browser.info/json/stations/topvote?limit=50&hidebroken=true`,
                    onload: resolve,
                    onerror: () => resolve({ responseText: '[]', status: 0 })
                });
            });

            console.log("[Random Station] Response status:", response.status);

            // Handle case where responseText might be undefined
            const responseText = response.responseText || '[]';
            console.log("[Random Station] Response text:", responseText.substring(0, 200) + "...");

            if (response.status !== 200) {
                console.error("[Random Station] HTTP error:", response.status);
                return null;
            }

            const stations = JSON.parse(responseText);
            console.log("[Random Station] Parsed stations:", stations.length);

            if (stations.length > 0) {
                // Filter out stations without valid URLs
                const validStations = stations.filter(station =>
                    station.url_resolved || station.url
                );

                if (validStations.length === 0) {
                    console.error("[Random Station] No valid stations found");
                    return null;
                }

                // Get a random station from the popular stations
                const randomIndex = Math.floor(Math.random() * validStations.length);
                const station = validStations[randomIndex];

                console.log("[Random Station] Selected station:", station.name);

                return {
                    name: station.name,
                    url: station.url_resolved || station.url,
                    format: station.codec || "mp3",
                    country: station.country,
                    language: station.language,
                    tags: station.tags,
                    favicon: station.favicon,
                    votes: station.votes,
                    bitrate: station.bitrate
                };
            }
            return null;
        } catch (e) {
            console.error("[Radio Browser] Random station error:", e);
            return null;
        }
    }

    // === SETTINGS PANEL ===
    const tab = await IRF.ui.panel.createTabFor(GM.info, {
        tabName: "Radio Selector",
        className: "radio-selector-tab"
    });

    // Add base styles to the container
    const style = document.createElement('style');
    style.textContent = `
        .radio-selector-tab {
            padding: 1.5rem;
            color: #fff;
            font-family: "Roboto", "Inter", "Segoe UI", sans-serif;
            position: relative;
        }
    `;
    document.head.appendChild(style);

    // Override Toggle Row
    const overrideRow = document.createElement("div");
    Object.assign(overrideRow.style, {
        display: "flex",
        justifyContent: "space-between",
        alignItems: "center",
        marginBottom: "-0.6rem"
    });

    const overrideLabel = document.createElement("label");
    overrideLabel.textContent = "Reset to default radio station";
    Object.assign(overrideLabel.style, {
        fontWeight: "500",
        fontSize: "0.95rem"
    });

    const overrideToggle = document.createElement("input");
    overrideToggle.type = "checkbox";
    overrideToggle.className = IRF.ui.panel.styles.toggle;
    overrideToggle.checked = isOverrideEnabled;

    overrideRow.appendChild(overrideLabel);
    overrideRow.appendChild(overrideToggle);
    tab.container.appendChild(overrideRow);

    // Divider
    const divider = document.createElement("hr");
    Object.assign(divider.style, {
        border: "none",
        borderTop: "1px solid rgba(255, 255, 255, 0.1)",
        margin: "1.3rem 0"
    });
    tab.container.appendChild(divider);

    // === BUTTONS CONTAINER ===
    const buttonsContainer = document.createElement("div");
    Object.assign(buttonsContainer.style, {
        display: "flex",
        flexWrap: "wrap",
        gap: "0.5rem",
        alignItems: "center",
        marginBottom: "1.5rem"
    });
    tab.container.appendChild(buttonsContainer);

    // Add Random Station Button
    const randomButton = document.createElement("button");
    randomButton.textContent = "🎲 Random Station";
    randomButton.title = "Switch to a random station";
    Object.assign(randomButton.style, {
        padding: "0.35rem 1rem",
        cursor: "pointer",
        border: "1px solid rgba(255, 255, 255, 0.3)",
        borderRadius: "999px",
        background: "rgba(68, 68, 170, 0.2)",
        color: "#fff",
        fontSize: "0.9rem",
        fontFamily: "inherit",
        transition: "all 0.2s ease",
        whiteSpace: "nowrap",
        marginBottom: "0.5rem"
    });

    randomButton.addEventListener("mouseenter", () => {
        randomButton.style.background = "rgba(68, 68, 170, 0.4)";
        randomButton.style.borderColor = "rgba(255, 255, 255, 0.5)";
    });
    randomButton.addEventListener("mouseleave", () => {
        randomButton.style.background = "rgba(68, 68, 170, 0.2)";
        randomButton.style.borderColor = "rgba(255, 255, 255, 0.3)";
    });

    randomButton.onclick = async () => {
        if (isChangingStation) return;
        isChangingStation = true;

        // Get available stations (exclude current one)
        const availableStations = allRadios.filter(station => station.name !== selectedStationName);

        if (availableStations.length === 0) {
            console.log("No other stations available");
            isChangingStation = false;
            return;
        }

        // Pick a random station
        const randomIndex = Math.floor(Math.random() * availableStations.length);
        const randomStation = availableStations[randomIndex];

        // Update selection
        selectedStation = randomStation;
        selectedStationName = randomStation.name;
        await GM_setValue("lastSelectedStation", selectedStationName);

        // Visual feedback
        randomButton.textContent = "🎲 Switching...";
        randomButton.style.background = "rgba(68, 68, 170, 0.6)";

        // Re-render buttons to show new selection
        renderButtons();

        // Reset button after delay
        setTimeout(() => {
            randomButton.textContent = "🎲 Random Station";
            randomButton.style.background = "rgba(68, 68, 170, 0.2)";
            isChangingStation = false;
        }, 1000);
    };

    buttonsContainer.appendChild(randomButton);

    let isChangingStation = false;

    // === RADIO BROWSER SEARCH SECTION ===
    const radioBrowserSection = document.createElement("div");
    Object.assign(radioBrowserSection.style, {
        marginTop: "1.5rem",
        marginBottom: "1.5rem"
    });

    const radioBrowserTitle = document.createElement("div");
    radioBrowserTitle.textContent = "🌐 Add Station through Radio Browser";
    Object.assign(radioBrowserTitle.style, {
        marginBottom: "1rem",
        fontWeight: "500",
        fontSize: "0.95rem"
    });
    radioBrowserSection.appendChild(radioBrowserTitle);

    // Search input
    const searchInput = document.createElement("input");
    searchInput.type = "text";
    searchInput.placeholder = "Search for stations (ex., 'jazz', 'rock', 'news')";
    const inputStyles = {
        background: "transparent",
        border: "1px solid rgba(255, 255, 255, 0.2)",
        borderRadius: "999px",
        padding: "0.5rem 1rem",
        color: "#fff",
        fontSize: "0.9rem",
        width: "95%",
        marginBottom: "0.75rem"
    };
    Object.assign(searchInput.style, inputStyles);
    radioBrowserSection.appendChild(searchInput);

    // Search button
    const searchButton = document.createElement("button");
    searchButton.textContent = "Search";
    Object.assign(searchButton.style, {
        padding: "0.5rem 1rem",
        cursor: "pointer",
        border: "1px solid #44a",
        borderRadius: "999px",
        background: "#44a",
        color: "#fff",
        fontSize: "0.9rem",
        fontFamily: "inherit",
        transition: "all 0.2s ease",
        marginTop: "0.5rem",
        marginRight: "0.5rem"
    });

    searchButton.addEventListener("mouseenter", () => {
        searchButton.style.background = "#55b";
        searchButton.style.borderColor = "#55b";
    });
    searchButton.addEventListener("mouseleave", () => {
        searchButton.style.background = "#44a";
        searchButton.style.borderColor = "#44a";
    });

    // Popular stations button
    const popularButton = document.createElement("button");
    popularButton.textContent = "Popular Stations";
    Object.assign(popularButton.style, {
        padding: "0.5rem 1rem",
        cursor: "pointer",
        border: "1px solid #44a",
        borderRadius: "999px",
        background: "#44a",
        color: "#fff",
        fontSize: "0.9rem",
        fontFamily: "inherit",
        transition: "all 0.2s ease",
        marginTop: "0.5rem"
    });

    popularButton.addEventListener("mouseenter", () => {
        popularButton.style.background = "#55b";
        popularButton.style.borderColor = "#55b";
    });
    popularButton.addEventListener("mouseleave", () => {
        popularButton.style.background = "#44a";
        popularButton.style.borderColor = "#44a";
    });

    // Random station button
    const randomStationButton = document.createElement("button");
    randomStationButton.textContent = "🎲 Random Station";
    Object.assign(randomStationButton.style, {
        padding: "0.5rem 1rem",
        cursor: "pointer",
        border: "1px solid #44a",
        borderRadius: "999px",
        background: "#44a",
        color: "#fff",
        fontSize: "0.9rem",
        fontFamily: "inherit",
        transition: "all 0.2s ease",
        marginTop: "0.5rem",
        marginLeft: "0.5rem"
    });

    randomStationButton.addEventListener("mouseenter", () => {
        randomStationButton.style.background = "#55b";
        randomStationButton.style.borderColor = "#55b";
    });
    randomStationButton.addEventListener("mouseleave", () => {
        randomStationButton.style.background = "#44a";
        randomStationButton.style.borderColor = "#44a";
    });

    randomStationButton.onclick = async () => {
        randomStationButton.textContent = "🎲 Loading...";
        randomStationButton.disabled = true;

        try {
            console.log("[Random Station] Starting to fetch random station...");
            const randomStation = await getRandomStation();

            if (randomStation) {
                console.log("[Random Station] Successfully fetched station:", randomStation.name);

                // Check if already exists
                if (allRadios.some(r => r.name === randomStation.name)) {
                    console.log("[Random Station] Station already exists:", randomStation.name);
                    alert("This station is already in your list! Try again for a different random station.");
                    randomStationButton.textContent = "🎲 Random Station";
                    randomStationButton.disabled = false;
                    return;
                }

                // Add station to custom radios
                const newStation = {
                    name: randomStation.name,
                    url: randomStation.url,
                    format: randomStation.format
                };

                console.log("[Random Station] Adding new station:", newStation);
                customRadios.push(newStation);
                allRadios.push(newStation);
                await GM_setValue("customRadios", customRadios);

                // Switch to the new station
                selectedStation = newStation;
                selectedStationName = randomStation.name;
                await GM_setValue("lastSelectedStation", selectedStationName);

                // Update UI
                renderButtons();

                alert(`Added random station "${randomStation.name}" from ${randomStation.country || 'Unknown'} to your stations!`);
            } else {
                console.error("[Random Station] Failed to fetch random station - returned null");
                alert("Failed to fetch a random station. Please check the browser console for details and try again.");
            }
        } catch (e) {
            console.error("[Random Station] Error adding random station:", e);
            alert(`Error adding random station: ${e.message}. Please check the browser console for details.`);
        } finally {
            randomStationButton.textContent = "🎲 Random Station";
            randomStationButton.disabled = false;
        }
    };

    // Results container
    const searchResultsContainer = document.createElement("div");
    Object.assign(searchResultsContainer.style, {
        marginTop: "1rem",
        maxHeight: "300px",
        overflowY: "auto",
        display: "none"
    });

    // Track if popular results are showing
    let popularResultsShowing = false;

    // Search functionality
    let searchTimeout;
    searchInput.addEventListener("input", () => {
        clearTimeout(searchTimeout);
        searchTimeout = setTimeout(async () => {
            const query = searchInput.value.trim();
            if (query.length >= 2) {
                searchButton.textContent = "Searching...";
                const results = await searchRadioBrowser(query);
                displaySearchResults(results);
                searchButton.textContent = "Search";
                // Reset popular button state
                popularResultsShowing = false;
                popularButton.textContent = "Popular Stations";
            } else {
                searchResultsContainer.style.display = "none";
            }
        }, 500);
    });

    searchButton.onclick = async () => {
        const query = searchInput.value.trim();
        if (!query) {
            alert("Please enter a search term");
            return;
        }

        searchButton.textContent = "Searching...";
        const results = await searchRadioBrowser(query);
        displaySearchResults(results);
        searchButton.textContent = "Search";
        // Reset popular button state
        popularResultsShowing = false;
        popularButton.textContent = "Popular Stations";
    };

    popularButton.onclick = async () => {
        if (popularResultsShowing) {
            // Close results
            searchResultsContainer.style.display = "none";
            popularResultsShowing = false;
            popularButton.textContent = "Popular Stations";
        } else {
            // Show popular results
            popularButton.textContent = "Loading...";
            const results = await getPopularStations();
            displaySearchResults(results);
            popularResultsShowing = true;
            popularButton.textContent = "Close Popular Stations";
        }
    };

    function displaySearchResults(stations) {
        searchResultsContainer.innerHTML = "";

        if (stations.length === 0) {
            searchResultsContainer.innerHTML = '<div style="color: rgba(255,255,255,0.6); text-align: center; padding: 1rem;">No stations found</div>';
            searchResultsContainer.style.display = "block";
            return;
        }

        stations.forEach(station => {
            const stationDiv = document.createElement("div");
            Object.assign(stationDiv.style, {
                padding: "0.75rem",
                border: "1px solid rgba(255, 255, 255, 0.1)",
                borderRadius: "6px",
                marginBottom: "0.5rem",
                background: "rgba(255, 255, 255, 0.05)",
                cursor: "pointer",
                transition: "all 0.2s ease"
            });

            stationDiv.addEventListener("mouseenter", () => {
                stationDiv.style.background = "rgba(255, 255, 255, 0.1)";
            });
            stationDiv.addEventListener("mouseleave", () => {
                stationDiv.style.background = "rgba(255, 255, 255, 0.05)";
            });

            const stationInfo = `
                <div style="font-weight: 500; margin-bottom: 0.25rem;">${station.name}</div>
                <div style="font-size: 0.8rem; color: rgba(255,255,255,0.7);">
                    ${station.country || 'Unknown'} • ${station.language || 'Unknown'} • ${station.bitrate || 'Unknown'}kbps
                </div>
                ${station.tags ? `<div style="font-size: 0.75rem; color: rgba(255,255,255,0.6); margin-top: 0.25rem;">${station.tags.split(',').slice(0, 3).join(', ')}</div>` : ''}
            `;

            stationDiv.innerHTML = stationInfo;

            stationDiv.onclick = async () => {
                // Add station to custom radios
                const newStation = {
                    name: station.name,
                    url: station.url,
                    format: station.format
                };

                // Check if already exists
                if (allRadios.some(r => r.name === station.name)) {
                    alert("This station is already in your list!");
                    return;
                }

                customRadios.push(newStation);
                allRadios.push(newStation);
                await GM_setValue("customRadios", customRadios);

                // Switch to the new station
                selectedStation = newStation;
                selectedStationName = station.name;
                await GM_setValue("lastSelectedStation", selectedStationName);

                // Update UI
                renderButtons();
                searchResultsContainer.style.display = "none";
                searchInput.value = "";
                popularResultsShowing = false;
                searchInput.value = "";
                popularButton.textContent = "Popular";

                alert(`Added "${station.name}" to your stations!`);
            };

            searchResultsContainer.appendChild(stationDiv);
        });

        searchResultsContainer.style.display = "block";
    }

    radioBrowserSection.appendChild(searchButton);
    radioBrowserSection.appendChild(popularButton);
    radioBrowserSection.appendChild(randomStationButton);
    radioBrowserSection.appendChild(searchResultsContainer);
    tab.container.appendChild(radioBrowserSection);

    function renderButtons() {
        buttonsContainer.innerHTML = "";
        allRadios.forEach(radio => {
            const isCustom = customRadios.some(r => r.name === radio.name && r.url === radio.url);

            const btnWrapper = document.createElement("div");
            Object.assign(btnWrapper.style, {
                position: "relative",
                display: "inline-block"
            });

            const btn = document.createElement("button");
            btn.textContent = radio.name;
            Object.assign(btn.style, {
                padding: "0.35rem 1rem",
                cursor: "pointer",
                border: "1px solid rgba(255, 255, 255, 0.2)",
                borderRadius: "999px",
                background: "transparent",
                color: "#fff",
                fontSize: "0.9rem",
                fontFamily: "inherit",
                transition: "all 0.2s ease",
                whiteSpace: "nowrap"
            });

            // Hover effect
            btn.addEventListener("mouseenter", () => {
                if (btn.textContent !== selectedStationName || isOverrideEnabled) {
                    btn.style.background = "rgba(68, 68, 170, 0.1)";
                }
            });
            btn.addEventListener("mouseleave", () => {
                if (btn.textContent !== selectedStationName || isOverrideEnabled) {
                    btn.style.background = "transparent";
                }
            });

            btn.onclick = async () => {
                if (isChangingStation || selectedStation === radio) return;
                isChangingStation = true;

                selectedStation = radio;
                selectedStationName = radio.name;
                await GM_setValue("lastSelectedStation", selectedStationName);
                highlightSelectedButton();

                setTimeout(() => {
                    isChangingStation = false;
                }, 500);
            };

            btnWrapper.appendChild(btn);

            if (isCustom) {
                const deleteBtn = document.createElement("div");
                deleteBtn.textContent = "×";
                deleteBtn.title = "Delete this station";
                Object.assign(deleteBtn.style, {
                    position: "absolute",
                    top: "-0.5rem",
                    right: "-0.25rem",
                    fontSize: "1.2rem",
                    color: "rgba(255, 255, 255, 0.8)",
                    cursor: "pointer",
                    zIndex: "2",
                    background: "rgba(0, 0, 0, 0.6)",
                    width: "20px",
                    height: "20px",
                    borderRadius: "50%",
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    transition: "all 0.2s ease",
                    opacity: "0",
                    visibility: "hidden"
                });

                deleteBtn.addEventListener("mouseenter", () => {
                    deleteBtn.style.background = "rgba(255, 0, 0, 0.8)";
                });
                deleteBtn.addEventListener("mouseleave", () => {
                    deleteBtn.style.background = "rgba(0, 0, 0, 0.6)";
                });

                deleteBtn.onclick = (e) => {
                    e.stopPropagation();
                    if (confirm(`Delete custom station "${radio.name}"?`)) {
                        customRadios = customRadios.filter(r => !(r.name === radio.name && r.url === radio.url));
                        GM_setValue("customRadios", customRadios);
                        allRadios.splice(allRadios.findIndex(r => r.name === radio.name && r.url === radio.url), 1);
                        renderButtons();
                    }
                };

                btnWrapper.appendChild(deleteBtn);

                const editBtn = document.createElement("div");
                editBtn.textContent = "✎";
                editBtn.title = "Edit this station";
                Object.assign(editBtn.style, {
                    position: "absolute",
                    top: "-0.5rem",
                    right: "1.25rem",
                    fontSize: "1rem",
                    color: "rgba(255, 255, 255, 0.8)",
                    cursor: "pointer",
                    zIndex: "2",
                    background: "rgba(0, 0, 0, 0.6)",
                    width: "20px",
                    height: "20px",
                    borderRadius: "50%",
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    lineHeight: "1",
                    paddingBottom: "2px",
                    transition: "all 0.2s ease",
                    opacity: "0",
                    visibility: "hidden"
                });

                editBtn.addEventListener("mouseenter", () => {
                    editBtn.style.background = "rgba(68, 68, 170, 0.8)";
                });
                editBtn.addEventListener("mouseleave", () => {
                    editBtn.style.background = "rgba(0, 0, 0, 0.6)";
                });

                editBtn.onclick = (e) => {
                    e.stopPropagation();
                    stationNameInput.value = radio.name;
                    streamInput.value = radio.url;
                    addButton.textContent = "Update Station";
                    addButton.dataset.editMode = "true";
                    addButton.dataset.originalName = radio.name;
                    addButton.dataset.originalUrl = radio.url;
                    stationNameInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
                };

                btnWrapper.appendChild(editBtn);

                // Show/hide buttons on hover
                btnWrapper.addEventListener("mouseenter", () => {
                    editBtn.style.opacity = "1";
                    editBtn.style.visibility = "visible";
                    deleteBtn.style.opacity = "1";
                    deleteBtn.style.visibility = "visible";
                });

                btnWrapper.addEventListener("mouseleave", () => {
                    editBtn.style.opacity = "0";
                    editBtn.style.visibility = "hidden";
                    deleteBtn.style.opacity = "0";
                    deleteBtn.style.visibility = "hidden";
                });
            }

            buttonsContainer.appendChild(btnWrapper);
        });
        highlightSelectedButton();
    }

    function highlightSelectedButton() {
        Array.from(buttonsContainer.querySelectorAll("button")).forEach(btn => {
            if (btn.textContent === selectedStationName && !isOverrideEnabled) {
                Object.assign(btn.style, {
                    backgroundColor: "#44a",
                    color: "#fff",
                    border: "1px solid #44a"
                });
            } else {
                Object.assign(btn.style, {
                    backgroundColor: "transparent",
                    color: "#fff",
                    border: "1px solid rgba(255, 255, 255, 0.2)"
                });
            }
        });
    }

    // === CUSTOM STATION SECTION ===
    const customSection = document.createElement("div");
    Object.assign(customSection.style, {
        marginTop: "1.5rem",
        paddingTop: "1.5rem",
        borderTop: "1px solid rgba(255, 255, 255, 0.1)"
    });

    const customTitle = document.createElement("div");
    customTitle.textContent = "📝 Add Station Manually";
    Object.assign(customTitle.style, {
        marginBottom: "1rem",
        fontWeight: "500",
        fontSize: "0.95rem"
    });
    customSection.appendChild(customTitle);

    const stationNameInput = document.createElement("input");
    stationNameInput.type = "text";
    stationNameInput.placeholder = "Station name";
    Object.assign(stationNameInput.style, inputStyles);
    customSection.appendChild(stationNameInput);

    const streamInput = document.createElement("input");
    streamInput.type = "text";
    streamInput.placeholder = "Stream URL";
    Object.assign(streamInput.style, inputStyles);
    customSection.appendChild(streamInput);

     // Stream URL hint
    const streamHint = document.createElement("div");
    Object.assign(streamHint.style, {
        fontSize: "0.8rem",
        color: "rgba(255, 255, 255, 0.6)",
        marginTop: "0rem",
        lineHeight: "1.4"
    });
    streamHint.innerHTML = 'Get Stream URLs from <a href="https://www.radio-browser.info" target="_blank" style="color: #44a; text-decoration: none;">radio-browser.info</a>, <a href="https://irt.crschmidt.net/radio.html" target="_blank" style="color: #44a; text-decoration: none;">irt.crschmidt.net/radio.html</a>. Archive of previous stations <a href="https://roadtrip.pikarocks.dev/stations" target="_blank" style="color: #44a; text-decoration: none;">roadtrip.pikarocks.dev/stations</a>.';
    customSection.appendChild(streamHint);
    tab.container.appendChild(customSection);

    const addButton = document.createElement("button");
    addButton.textContent = "Add Station";
    Object.assign(addButton.style, {
        padding: "0.5rem 1rem",
        cursor: "pointer",
        border: "1px solid #44a",
        borderRadius: "999px",
        background: "#44a",
        color: "#fff",
        fontSize: "0.9rem",
        fontFamily: "inherit",
        transition: "all 0.2s ease",
        marginTop: "0.5rem"
    });

    addButton.addEventListener("mouseenter", () => {
        addButton.style.background = "#55b";
        addButton.style.borderColor = "#55b";
    });
    addButton.addEventListener("mouseleave", () => {
        addButton.style.background = "#44a";
        addButton.style.borderColor = "#44a";
    });

    customSection.appendChild(addButton);

    // Event Listeners
    overrideToggle.addEventListener("change", async () => {
        isOverrideEnabled = overrideToggle.checked;
        await GM_setValue("isOverrideEnabled", isOverrideEnabled);
        console.log(`Radio override ${isOverrideEnabled ? "Reset to default station" : `custom station active — ${selectedStation.name}`}.`);
        highlightSelectedButton();

        // Force an update of the radio station
        const container = await IRF.vdom.container;
        if (container && container.methods.updateData) {
            container.methods.updateData.call(container.state, container.state.data);
        }
    });

    addButton.onclick = async () => {
        const name = stationNameInput.value.trim();
        const url = streamInput.value.trim();

        if (!name || !url) {
            alert("Please fill in both the station name and stream URL.");
            return;
        }

        // Validate URL format
        try {
            new URL(url);
        } catch (e) {
            alert("Please enter a valid URL");
            return;
        }

        const isEditMode = addButton.dataset.editMode === "true";
        const originalName = addButton.dataset.originalName;
        const originalUrl = addButton.dataset.originalUrl;

        // Check for duplicate names, but exclude the station being edited
        if (!isEditMode && allRadios.some(r => r.name === name)) {
            alert("A station with this name already exists.");
            return;
        }

        if (isEditMode) {
            // Remove the old station
            customRadios = customRadios.filter(r => !(r.name === originalName && r.url === originalUrl));
            allRadios.splice(allRadios.findIndex(r => r.name === originalName && r.url === originalUrl), 1);
        }

        const newStation = { name, url };
        customRadios.push(newStation);
        allRadios.push(newStation);
        await GM_setValue("customRadios", customRadios);

        // If we were editing the currently selected station, update the selection
        if (isEditMode && selectedStationName === originalName) {
            selectedStation = newStation;
            selectedStationName = name;
            await GM_setValue("lastSelectedStation", selectedStationName);
        }

        // Reset the form
        stationNameInput.value = "";
        streamInput.value = "";
        addButton.textContent = "Add Station";
        addButton.dataset.editMode = "false";
        delete addButton.dataset.originalName;
        delete addButton.dataset.originalUrl;

        renderButtons();

        // Force an update of the radio station if necessary
        if (isEditMode && selectedStationName === name) {
            const container = await IRF.vdom.container;
            if (container && container.methods.updateData) {
                container.methods.updateData.call(container.state, container.state.data);
            }
        }
    };

    renderButtons();

    // Start fetching station info
    await updateAllInfo();
    setInterval(updateAllInfo, 30000);

    function updatePopupContent() {
        IRF.vdom.radio.then(radio => {
            const currentStation = radio.state.stationName;
            const info = nowPlayingInfo[currentStation];

            if (!info) {
                infoPopup.innerHTML = '<div>No additional information available for this station.</div>';
                return;
            }

            if (currentStation === 'WBOR 91.1 FM') {
                let liveShowText = "No live shows currently";
                if (liveDjName && !/wbor'?s commodore\s*64/i.test(liveDjName.trim())) {
                    liveShowText = `Live Show: ${liveShowName}${liveDjName ? ` with ${liveDjName}` : ""}`;
                }

                infoPopup.innerHTML = `
                    <div><strong>Now Playing:</strong> ${info.nowPlaying}</div>
                    <div style="margin-top: 8px;"><strong>${liveShowText}</strong></div>
                `;
            } else if (currentStation === 'Folk\'d Up Radio' || currentStation === 'CBFM Radio') {
                infoPopup.innerHTML = `
                    <div><strong>Now Playing:</strong> ${info.nowPlaying}</div>
                `;
            }
        });
    }

    function positionPopup() {
        // Wait for content to be rendered and dimensions to be calculated
        requestAnimationFrame(() => {
            const btnRect = infoButton.getBoundingClientRect();
            const popupRect = infoPopup.getBoundingClientRect();
            let left = btnRect.left - popupRect.width - 8;
            let top = btnRect.top + (btnRect.height / 2) - (popupRect.height / 2);

            if (left < 8) {
                left = btnRect.right + 8;
            }
            if (top < 8) top = 8;
            if (top + popupRect.height > window.innerHeight - 8) {
                top = window.innerHeight - popupRect.height - 8;
            }

            infoPopup.style.left = `${left}px`;
            infoPopup.style.top = `${top}px`;
        });
    }

    infoButton.addEventListener("mouseenter", () => {
        if (isPopupOpen) return;
        const rect = infoButton.getBoundingClientRect();
        tooltipSpan.style.left = `${rect.left - tooltipSpan.offsetWidth - 8}px`;
        tooltipSpan.style.top = `${rect.top + (rect.height / 2) - 10}px`;
        tooltipSpan.style.opacity = "1";
        tooltipSpan.style.visibility = "visible";
    });

    infoButton.addEventListener("mouseleave", () => {
        tooltipSpan.style.opacity = "0";
        tooltipSpan.style.visibility = "hidden";
    });

    infoButton.addEventListener("click", () => {
        isPopupOpen = !isPopupOpen;
        if (isPopupOpen) {
            updatePopupContent();
            // First make the popup visible but transparent for proper dimension calculation
            infoPopup.style.visibility = "visible";
            infoPopup.style.opacity = "0";

            // Position the popup after content is updated
            requestAnimationFrame(() => {
                positionPopup();
                // Now fade it in
                requestAnimationFrame(() => {
                    infoPopup.style.opacity = "1";
                });
            });

            tooltipSpan.style.opacity = "0";
            tooltipSpan.style.visibility = "hidden";
        } else {
            infoPopup.style.opacity = "0";
            infoPopup.style.visibility = "hidden";
        }
    });

    document.addEventListener("click", (e) => {
        if (!infoPopup.contains(e.target) && e.target !== infoButton) {
            infoPopup.style.opacity = "0";
            infoPopup.style.visibility = "hidden";
            isPopupOpen = false;
        }
    });

    window.addEventListener("resize", () => {
        if (infoPopup.style.visibility === "visible") {
            positionPopup();
        }
    });
})();