Riffusion Song Deletion Automation

Robustly auto-deletes songs. Starts minimized. Uses Song IDs for reliable selective deletion. UI toggle, draggable & MINIMIZABLE UI (Top-Right), No-Scroll Delete All, No confirm click. Filters (keywords input, liked). USE WITH CAUTION.

// ==UserScript==
// @name         Riffusion Song Deletion Automation
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Robustly auto-deletes songs. Starts minimized. Uses Song IDs for reliable selective deletion. UI toggle, draggable & MINIMIZABLE UI (Top-Right), No-Scroll Delete All, No confirm click. Filters (keywords input, liked). USE WITH CAUTION.
// @author       Graph1ks (assisted by GoogleAI)
// @match        https://www.riffusion.com/library/my-songs
// @grant        GM_addStyle
// @grant        GM_info
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---
    const INITIAL_MODE = 'selective';
    const DELETION_DELAY = 500;
    const DROPDOWN_DELAY = 300;
    const MAX_RETRIES = 3;
    const MAX_EMPTY_CHECKS = 3;
    const EMPTY_RETRY_DELAY = 6000;
    const KEYWORD_FILTER_DEBOUNCE = 500;
    const UI_INITIAL_TOP = '60px'; // Default position when opened
    const UI_INITIAL_RIGHT = '20px';// Default position when opened
    const INITIAL_IGNORE_LIKED = true;
    const MINIMIZED_ICON_SIZE = '40px';
    const MINIMIZED_ICON_TOP = '15px'; // Position from top when minimized
    const MINIMIZED_ICON_RIGHT = '15px'; // Position from right when minimized

    let debugMode = false;
    let isDeleting = false;
    let currentMode = INITIAL_MODE;
    let ignoreLikedSongsState = INITIAL_IGNORE_LIKED;
    let keywordFilterDebounceTimer = null;

    // --- State for Minimize/Restore ---
    let isMinimized = true; // Start minimized
    let lastUiTop = UI_INITIAL_TOP;
    let lastUiLeft = null;
    let uiElement = null;
    let minimizedIconElement = null;

    // --- Styling ---
    GM_addStyle(`
        #riffDelControlUI {
            position: fixed; background: linear-gradient(145deg, #2a2a2a, #1e1e1e); border: 1px solid #444; border-radius: 12px; padding: 0; z-index: 10000; width: 290px; box-shadow: 0 8px 16px rgba(0, 0, 0, 0.5); color: #e0e0e0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; user-select: none; overflow: hidden;
            display: ${isMinimized ? 'none' : 'block'}; /* Initial visibility */
        }
        #riffDelHeader {
            background: linear-gradient(90deg, #3a3a3a, #2c2c2c); padding: 10px 15px; cursor: move; border-bottom: 1px solid #444; border-radius: 12px 12px 0 0; position: relative;
        }
        #riffDelHeader h3 { margin: 0; font-size: 16px; font-weight: 600; color: #ffffff; text-align: center; text-shadow: 0 1px 2px rgba(0,0,0,0.3); padding-right: 25px; /* Space for minimize btn */ }

        /* Minimize Button */
        #minimizeButton {
            position: absolute; top: 5px; right: 8px; background: none; border: none; color: #aaa; font-size: 20px; font-weight: bold; line-height: 1; cursor: pointer; padding: 2px 5px; border-radius: 4px; transition: color 0.2s, background-color 0.2s;
        }
        #minimizeButton:hover { color: #fff; background-color: rgba(255, 255, 255, 0.1); }

        /* Minimized Icon */
        #riffDelMinimizedIcon {
            position: fixed; top: ${MINIMIZED_ICON_TOP}; right: ${MINIMIZED_ICON_RIGHT}; width: ${MINIMIZED_ICON_SIZE}; height: ${MINIMIZED_ICON_SIZE}; background: linear-gradient(145deg, #3a3a3a, #2c2c2c); border: 1px solid #555; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); color: #e0e0e0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 16px; font-weight: bold; display: ${isMinimized ? 'flex' : 'none'}; align-items: center; justify-content: center; cursor: pointer; z-index: 10001; transition: background 0.2s; user-select: none;
        }
        #riffDelMinimizedIcon:hover { background: linear-gradient(145deg, #4a4a4a, #3c3c3c); }

        /* Content Styles */
        #riffDelContent { padding: 15px; }
        .riffDelButton { display: block; border: none; border-radius: 8px; padding: 10px; font-size: 14px; font-weight: 500; text-align: center; cursor: pointer; transition: transform 0.2s, background 0.2s; width: 100%; margin-bottom: 10px; }
        .riffDelButton:hover:not(:disabled) { transform: translateY(-2px); }
        .riffDelButton:disabled { background: #555 !important; cursor: not-allowed; transform: none; }
        #deleteAllButton { background: linear-gradient(90deg, #ff4d4d, #e63939); color: #fff; }
        #deleteAllButton:hover:not(:disabled) { background: linear-gradient(90deg, #e63939, #cc3333); }
        #deleteButton { background: linear-gradient(90deg, #ff4d4d, #e63939); color: #fff; }
        #deleteButton:hover:not(:disabled) { background: linear-gradient(90deg, #e63939, #cc3333); }
        #reloadButton { background: linear-gradient(90deg, #1db954, #17a34a); color: #fff; }
        #reloadButton:hover:not(:disabled) { background: linear-gradient(90deg, #17a34a, #158a3f); }
        #modeToggle { background: linear-gradient(90deg, #4d94ff, #3385ff); color: #fff; }
        #modeToggle:hover:not(:disabled) { background: linear-gradient(90deg, #3385ff, #1a75ff); }
        #debugToggle { background: linear-gradient(90deg, #6666ff, #4d4dff); color: #fff; }
        #debugToggle:hover:not(:disabled) { background: linear-gradient(90deg, #4d4dff, #3333cc); }
        #statusMessage { margin-top: 10px; font-size: 13px; color: #1db954; text-align: center; min-height: 1.2em; word-wrap: break-word; }
        #selectiveModeControls { display: none; } /* Default hidden */
        #bulkModeControls { display: none; } /* Default hidden */
        #songList { margin-bottom: 15px; max-height: 30vh; overflow-y: auto; padding-right: 5px; }
        #songList label { display: flex; align-items: center; margin: 8px 0; color: #d0d0d0; font-size: 14px; transition: color 0.2s; }
        #songList label:hover:not(.ignored) { color: #ffffff; }
        #songList input[type="checkbox"] { margin-right: 10px; accent-color: #1db954; width: 16px; height: 16px; cursor: pointer; }
        #songList input[type="checkbox"]:disabled { cursor: not-allowed; accent-color: #555; }
        #songList label.ignored { color: #777; cursor: not-allowed; font-style: italic;}
        #selectAllContainer { margin-bottom: 10px; display: flex; align-items: center; color: #d0d0d0; font-size: 14px; font-weight: 500; cursor: pointer; }
        #selectAllContainer input[type="checkbox"] { margin-right: 10px; accent-color: #1db954; width: 16px; height: 16px; }
        #selectAllContainer:hover { color: #ffffff; }
        #deleteCounter { margin-bottom: 10px; font-size: 14px; color: #1db954; text-align: center; }
        #songList::-webkit-scrollbar { width: 8px; }
        #songList::-webkit-scrollbar-track { background: #333; border-radius: 4px; }
        #songList::-webkit-scrollbar-thumb { background: #555; border-radius: 4px; }
        #songList::-webkit-scrollbar-thumb:hover { background: #777; }
        #filterSettings { margin-top: 10px; margin-bottom: 10px; padding-top: 10px; border-top: 1px solid #444; }
        #filterSettings label { display: flex; align-items: center; font-size: 13px; color: #ccc; cursor: pointer; margin-bottom: 8px; }
        #filterSettings label:hover { color: #fff; }
        #filterSettings input[type="checkbox"] { margin-right: 8px; accent-color: #1db954; width: 15px; height: 15px; cursor: pointer; }
        #keywordFilterInput { width: 100%; background-color: #333; border: 1px solid #555; color: #ddd; padding: 6px 10px; border-radius: 6px; font-size: 13px; box-sizing: border-box; margin-top: 5px; }
        #keywordFilterInput:focus { outline: none; border-color: #777; }
    `);

    // --- Helper Functions ---
    function debounce(func, wait) { let t; return function(...a) { const l=()=> { clearTimeout(t); func.apply(this,a); }; clearTimeout(t); t=setTimeout(l, wait); }; }
    function log(m, l='info') { const p="[RiffDel]"; if(l==='error') console.error(`${p} ${m}`); else if(l==='warn') console.warn(`${p} ${m}`); else console.log(`${p} ${m}`); if(debugMode&&l!=='info') console.log(`${p} [DEBUG] ${m}`); updateStatusMessage(m); }
    function logDebug(m, e=null) { if(!debugMode) return; console.log(`[RiffDel DEBUG] ${m}`, e?e.outerHTML.substring(0,150)+'...':''); }
    function updateStatusMessage(m) { const s=document.getElementById('statusMessage'); if(s) s.textContent = m.length>100 ? `... ${m.substring(m.length-100)}` : m; }
    function simulateClick(e) { if (!e) { logDebug('Element null for click'); return false; } try { ['pointerdown','mousedown','pointerup','mouseup','click'].forEach(t => e.dispatchEvent(new MouseEvent(t,{bubbles:true,cancelable:true}))); if(typeof e.click==='function') e.click(); logDebug('Sim Click:', e); return true; } catch (err) { log(`Click fail: ${err.message}`, 'error'); console.error('[RiffDel] Click details:', err, e); return false; } }
    function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

    // --- UI Functions ---

    function createMainUI() {
        uiElement = document.createElement('div');
        uiElement.id = 'riffDelControlUI';
        if (UI_INITIAL_RIGHT) {
            const rightPx = parseInt(UI_INITIAL_RIGHT, 10);
            const widthPx = 290;
            lastUiLeft = `${Math.max(0, window.innerWidth - rightPx - widthPx)}px`;
            uiElement.style.left = lastUiLeft;
            uiElement.style.right = 'auto';
        } else {
            lastUiLeft = '20px';
            uiElement.style.left = lastUiLeft;
        }
        lastUiTop = UI_INITIAL_TOP;
        uiElement.style.top = lastUiTop;
        uiElement.style.display = isMinimized ? 'none' : 'block';

        uiElement.innerHTML = `
            <div id="riffDelHeader">
                <h3>Riffusion Deleter</h3> <!-- Panel title, kept short -->
                <button id="minimizeButton" title="Minimize UI">_</button>
            </div>
            <div id="riffDelContent">
                <div id="selectiveModeControls">
                    <label id="selectAllContainer"><input type="checkbox" id="selectAll"> Select All Valid</label>
                    <div id="songList">Loading...</div>
                    <div id="deleteCounter">Deleted: 0 / 0</div>
                    <button id="deleteButton" class="riffDelButton">Delete Selected</button>
                    <button id="reloadButton" class="riffDelButton">Reload Songs</button>
                    <div id="filterSettings">
                        <label><input type="checkbox" id="ignoreLikedToggle"> Ignore Liked Songs</label>
                        <input type="text" id="keywordFilterInput" placeholder="Keywords to ignore (comma-sep)...">
                    </div>
                </div>
                <div id="bulkModeControls">
                    <button id="deleteAllButton" class="riffDelButton">Delete Entire Library</button>
                    <p style="font-size:12px;color:#aaa;text-align:center;margin-top:5px;">Deletes all songs without scrolling. Retries if list empty.</p>
                </div>
                <button id="modeToggle" class="riffDelButton">Switch Mode</button>
                <button id="debugToggle" class="riffDelButton">${debugMode?'Disable Debug':'Enable Debug'}</button>
                <div id="statusMessage">Ready.</div>
            </div>`;
        document.body.appendChild(uiElement);

        minimizedIconElement = document.createElement('div');
        minimizedIconElement.id = 'riffDelMinimizedIcon';
        minimizedIconElement.textContent = 'RD';
        minimizedIconElement.title = 'Restore Riffusion Deleter'; // Tooltip for icon
        minimizedIconElement.style.display = isMinimized ? 'flex' : 'none';
        document.body.appendChild(minimizedIconElement);

        const header = uiElement.querySelector('#riffDelHeader');
        enableDrag(uiElement, header);
        document.getElementById('minimizeButton')?.addEventListener('click', minimizeUI);
        minimizedIconElement?.addEventListener('click', restoreUI);
        document.getElementById('selectAll')?.addEventListener('change', toggleSelectAll);
        document.getElementById('deleteButton')?.addEventListener('click', deleteSelectedSongs);
        document.getElementById('reloadButton')?.addEventListener('click', () => {if (currentMode === 'selective') populateSongList();});
        document.getElementById('deleteAllButton')?.addEventListener('click', () => {if (isDeleting) { log("Deletion in progress.", "warn"); return; } if (confirm("ARE YOU SURE? This will attempt to delete ALL songs in your library without scrolling. NO UNDO.")) { deleteAllSongsInLibrary(); }});
        document.getElementById('modeToggle').addEventListener('click', toggleMode);
        document.getElementById('debugToggle').addEventListener('click', toggleDebug);
        const ignoreLikedToggle = document.getElementById('ignoreLikedToggle');
        if (ignoreLikedToggle) { ignoreLikedToggle.checked = ignoreLikedSongsState; ignoreLikedToggle.addEventListener('change', (e) => { ignoreLikedSongsState = e.target.checked; log(`Ignore Liked Songs: ${ignoreLikedSongsState}`); populateSongList(); });}
        const keywordInput = document.getElementById('keywordFilterInput');
        if (keywordInput) { keywordInput.addEventListener('input', debounce(() => { log('Keywords changed, refreshing list...'); populateSongList(); }, KEYWORD_FILTER_DEBOUNCE)); }

        updateUIVisibility();
    }

    // Hides the main UI and shows the minimized icon
    function minimizeUI() {
        if (!uiElement || !minimizedIconElement) return;
        if (!isMinimized) {
             lastUiTop = uiElement.style.top || UI_INITIAL_TOP;
             lastUiLeft = uiElement.style.left || lastUiLeft;
        }
        uiElement.style.display = 'none';
        minimizedIconElement.style.display = 'flex';
        isMinimized = true;
        logDebug("UI Minimized");
    }

    // Hides the minimized icon and shows the main UI at its last position
    function restoreUI() {
        if (!uiElement || !minimizedIconElement) return;
        minimizedIconElement.style.display = 'none';
        uiElement.style.display = 'block';
        uiElement.style.top = lastUiTop;
        uiElement.style.left = lastUiLeft;
        uiElement.style.right = 'auto';
        isMinimized = false;
        logDebug("UI Restored to:", { top: lastUiTop, left: lastUiLeft });
        // Ensure the correct mode's content is visible immediately upon restoring
        updateUIVisibility();
    }

    // Controls which part of the UI content is visible based on the current mode
    function updateUIVisibility() {
        if (isMinimized) return;

        const selectiveControls = document.getElementById('selectiveModeControls');
        const bulkControls = document.getElementById('bulkModeControls');
        const modeToggleButton = document.getElementById('modeToggle');
        const headerTitle = document.querySelector('#riffDelHeader h3');

        if (!selectiveControls || !bulkControls || !modeToggleButton || !headerTitle) {
            log("UI elements missing during visibility update.", "error");
            return;
        }

        logDebug(`Updating UI Visibility for mode: ${currentMode}`);
        if (currentMode === 'selective') {
            selectiveControls.style.display = 'block';
            bulkControls.style.display = 'none';
            modeToggleButton.textContent = 'Switch to Bulk Mode';
            headerTitle.textContent = 'Riffusion Deleter (Selective)'; // Panel title reflects mode
            const songList = document.getElementById('songList');
             if (!songList || songList.innerHTML === '' || songList.innerHTML === 'Loading...' || songList.children.length === 0) {
                populateSongList();
             }
            const ignoreLiked = document.getElementById('ignoreLikedToggle');
            if (ignoreLiked) ignoreLiked.checked = ignoreLikedSongsState;
        } else { // Bulk mode
            selectiveControls.style.display = 'none';
            bulkControls.style.display = 'block';
            modeToggleButton.textContent = 'Switch to Selective Mode';
            headerTitle.textContent = 'Riffusion Deleter (Bulk)'; // Panel title reflects mode
        }
        const statusMsg = document.getElementById('statusMessage');
        if (statusMsg) statusMsg.style.display = 'block';
    }

    // Switches between Selective and Bulk deletion modes
    function toggleMode() {
        if (isDeleting) { log("Cannot switch mode while deleting.", "warn"); return; }
        currentMode = (currentMode === 'selective') ? 'bulk' : 'selective';
        log(`Switched to ${currentMode} mode.`);
        updateUIVisibility();
    }

    // Toggles debug logging
    function toggleDebug() {
        debugMode = !debugMode;
        const btn = document.getElementById('debugToggle');
        if (btn) btn.textContent = debugMode ? 'Disable Debug' : 'Enable Debug';
        log(`Debug mode ${debugMode ? 'enabled' : 'disabled'}.`);
    }

    // Enables dragging the UI by its header
    function enableDrag(element, handle) {
        let isDragging = false, offsetX, offsetY;
        handle.addEventListener('mousedown', (e) => {
            if (e.button !== 0 || e.target.closest('button')) return;
            if (isMinimized) return;
            isDragging = true;
            const rect = element.getBoundingClientRect();
            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;
            element.style.cursor = 'grabbing';
            handle.style.cursor = 'grabbing';
            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp, { once: true });
            e.preventDefault();
        });
        function onMouseMove(e) {
            if (!isDragging) return;
            let newX = e.clientX - offsetX;
            let newY = e.clientY - offsetY;
            const winWidth = window.innerWidth;
            const winHeight = window.innerHeight;
            const elWidth = element.offsetWidth;
            const elHeight = element.offsetHeight;
            if (newX < 0) newX = 0;
            if (newY < 0) newY = 0;
            if (newX + elWidth > winWidth) newX = winWidth - elWidth;
            if (newY + elHeight > winHeight) newY = winHeight - elHeight;
            element.style.left = `${newX}px`;
            element.style.top = `${newY}px`;
            element.style.right = 'auto';
        }
        function onMouseUp(e) {
            if (e.button !== 0 || !isDragging) return;
            isDragging = false;
            element.style.cursor = 'default';
            handle.style.cursor = 'move';
            document.removeEventListener('mousemove', onMouseMove);
             if (!isMinimized) {
                 lastUiTop = element.style.top;
                 lastUiLeft = element.style.left;
                 logDebug("Stored new position after drag:", { top: lastUiTop, left: lastUiLeft });
             }
        }
    }

    // --- Selective Mode Functions ---
    function populateSongList() {
         if (currentMode !== 'selective' || isMinimized) return;
         logDebug('Populating song list with filters...');
         const songListDiv = document.getElementById('songList');
         const deleteCounter = document.getElementById('deleteCounter');
         if (!songListDiv) return;
         songListDiv.innerHTML = 'Loading...';
         if(deleteCounter) deleteCounter.textContent = 'Deleted: 0 / 0';
         const selectAllCheckbox = document.getElementById('selectAll');
         if (selectAllCheckbox) selectAllCheckbox.checked = false;
         const ignoreLikedCheckbox = document.getElementById('ignoreLikedToggle');
         if(ignoreLikedCheckbox) ignoreLikedCheckbox.checked = ignoreLikedSongsState;
         const keywordInput = document.getElementById('keywordFilterInput');
         const keywordString = keywordInput ? keywordInput.value : '';
         const dynamicIgnoreKeywords = keywordString.split(',').map(k => k.trim().toLowerCase()).filter(k => k !== '');
         logDebug(`Keywords to ignore: [${dynamicIgnoreKeywords.join(', ')}]`);
         setTimeout(() => {
             const songElements = getCurrentSongElements();
             logDebug(`Found ${songElements.length} song elements on page.`);
             songListDiv.innerHTML = '';
             if (songElements.length === 0) {
                 songListDiv.innerHTML = '<p style="color:#d0d0d0;text-align:center;">No songs found or loaded yet.</p>';
                 return;
             }
             let ignoredCount = 0;
             songElements.forEach((songElement, index) => {
                 const titleLink = songElement.querySelector('a[href^="/song/"]');
                 const titleH4 = titleLink ? titleLink.querySelector('h4.text-primary') : null;
                 const title = titleH4 ? titleH4.textContent.trim() : `Untitled Song ${index + 1}`;
                 const titleLower = title.toLowerCase();
                 let songId = null;
                 if (titleLink) {
                      const match = titleLink.href.match(/\/song\/([a-f0-9-]+)/);
                      if (match && match[1]) songId = match[1];
                 }
                 if (!songId) {
                     log(`Could not extract song ID for element at index ${index} ('${title}'). Skipping.`, "warn");
                     return;
                 }
                 const keywordMatch = dynamicIgnoreKeywords.length > 0 && dynamicIgnoreKeywords.some(keyword => titleLower.includes(keyword));
                 const likedIcon = songElement.querySelector('svg[data-prefix="fas"][data-icon="heart"]');
                 const isLiked = ignoreLikedSongsState && likedIcon !== null;
                 const shouldIgnore = keywordMatch || isLiked;
                 let ignoreReason = '';
                 if (keywordMatch) ignoreReason += 'Keyword';
                 if (isLiked) ignoreReason += (keywordMatch ? ' & Liked' : 'Liked');
                 const label = document.createElement('label');
                 label.innerHTML = `<input type="checkbox" data-song-id="${songId}" ${shouldIgnore ? 'disabled' : ''}> ${title}`;
                 if (shouldIgnore) {
                    label.classList.add('ignored');
                    label.title = `Ignoring: ${ignoreReason}`;
                    ignoredCount++;
                    logDebug(`Ignoring song ${songId} ('${title}'): ${ignoreReason}`);
                 }
                 songListDiv.appendChild(label);
             });
             logDebug(`Successfully populated ${songElements.length} songs into UI. Ignored ${ignoredCount}.`);
             updateStatusMessage(ignoredCount > 0 ? `Loaded ${songElements.length} (${ignoredCount} ignored)` : `Loaded ${songElements.length} songs.`);
         }, 100);
     }
    function toggleSelectAll(e) {
        if(currentMode !== 'selective' || isMinimized) return;
        const isChecked = e.target.checked;
        const checkboxes = document.querySelectorAll('#songList input[type="checkbox"]:not(:disabled)');
        checkboxes.forEach(cb => cb.checked = isChecked);
        logDebug(`Select All Toggled: ${isChecked} (${checkboxes.length} items affected)`);
    }
    function updateCounter(count, total) {
        if(currentMode !== 'selective' || isMinimized) return;
        const counterElement = document.getElementById('deleteCounter');
        if(counterElement) counterElement.textContent = `Deleted: ${count} / ${total}`;
        logDebug(`Counter Updated: ${count}/${total}`);
    }

    // --- Deletion Logic ---
    function getCurrentSongElements() {
        const listContainer = document.querySelector('div[data-sentry-component="InfiniteScroll"] > div.grow');
        if(listContainer) return listContainer.querySelectorAll(':scope > div[data-sentry-component="DraggableRiffRow"]');
        log("Warning: Specific list container not found, using fallback selector.", "warn");
        return document.querySelectorAll('div[data-sentry-component="DraggableRiffRow"]');
    }
    async function deleteSelectedSongs() {
        if (isMinimized) { log("Please restore the UI to delete.", "warn"); return; }
        if (currentMode !== 'selective') { log("Selective delete only available in Selective Mode.", "warn"); return; }
        if (isDeleting) { log("Deletion already in progress.", "warn"); return; }
        const checkboxes = document.querySelectorAll('#songList input[type="checkbox"]:checked');
        const totalToDelete = checkboxes.length;
        if (totalToDelete === 0) {
            updateCounter(0, 0);
            log('No valid songs selected for deletion.');
            updateStatusMessage('No songs selected or all selected are ignored.');
            return;
        }
        isDeleting = true;
        setButtonsDisabled(true);
        const songIdsToDelete = Array.from(checkboxes).map(cb => cb.dataset.songId);
        log(`Starting deletion for ${totalToDelete} selected song IDs: [${songIdsToDelete.join(', ')}]`);
        updateCounter(0, totalToDelete);
        updateStatusMessage(`Deleting ${totalToDelete} selected...`);
        let deletedCount = 0;
        let criticalErrorOccurred = false;
        for (const songId of songIdsToDelete) {
            logDebug(`Processing Song ID: ${songId}`);
            if (criticalErrorOccurred || !isDeleting) {
                 log(`Stopping deletion loop. CritErr: ${criticalErrorOccurred}, IsDeleting: ${isDeleting}`, "warn");
                 break;
            }
            const songElement = document.querySelector(`div[data-sentry-component="DraggableRiffRow"] a[href="/song/${songId}"]`)?.closest('div[data-sentry-component="DraggableRiffRow"]');
            if (!songElement) {
                log(`Song row for ID ${songId} not found on page (already deleted?). Skipping.`, "warn");
                const checkboxToRemove = document.querySelector(`#songList input[data-song-id="${songId}"]`);
                checkboxToRemove?.closest('label')?.remove();
                continue;
            }
            const titleH4 = songElement.querySelector('h4.text-primary');
            const title = titleH4 ? titleH4.textContent.trim() : `song ID ${songId}`;
            logDebug(`Found element for ${title} (ID: ${songId}). Attempting delete...`);
            const success = await processSingleDeletion(songElement, songId);
            logDebug(`processSingleDeletion result for ID ${songId}: ${success}`);
            if (success) {
                deletedCount++;
                updateCounter(deletedCount, totalToDelete);
                updateStatusMessage(`Deleted ${deletedCount}/${totalToDelete}...`);
                logDebug(`Successfully processed ID ${songId}. Count: ${deletedCount}`);
                 const checkboxToRemove = document.querySelector(`#songList input[data-song-id="${songId}"]`);
                 checkboxToRemove?.closest('label')?.remove();
            } else {
                log(`Failed to delete ${title} (ID: ${songId}). Stopping selective delete process.`, "error");
                updateStatusMessage(`Error deleting ${title}. Stopped.`);
                criticalErrorOccurred = true;
            }
            logDebug(`Loop iteration end for ID: ${songId}. Critical Error: ${criticalErrorOccurred}`);
            await delay(50);
        }
        log(`Selective deletion loop finished. ${deletedCount} of ${totalToDelete} songs attempted.`);
        updateStatusMessage(criticalErrorOccurred ? `Deletion stopped due to error. ${deletedCount} deleted.` : `Selected deletion complete. ${deletedCount} deleted.`);
        isDeleting = false;
        setButtonsDisabled(false);
    }
     async function deleteAllSongsInLibrary() {
         if (isMinimized) { log("Please restore the UI to delete.", "warn"); return; }
         if (currentMode !== 'bulk') { log("Bulk delete only in Bulk Mode.", "warn"); return; }
         if (isDeleting) { log("Deletion in progress.", "warn"); return; }
         isDeleting = true;
         setButtonsDisabled(true);
         log("--- STARTING LIBRARY DELETION (Bulk Mode / No-Scroll) ---");
         updateStatusMessage("Starting full library deletion...");
         let totalDeleted = 0, emptyChecks = 0;
         while (isDeleting) {
             await delay(500);
             if (!isDeleting) { log("Deletion stopped externally.", "warn"); break; }
             let currentElements = getCurrentSongElements();
             let currentSize = currentElements.length;
             log(`Checking for songs... Found ${currentSize}.`);
             if (currentSize === 0) {
                 log(`No songs found. Waiting ${EMPTY_RETRY_DELAY / 1000}s (Check ${emptyChecks + 1}/${MAX_EMPTY_CHECKS})...`);
                 updateStatusMessage(`No songs. Re-checking in ${EMPTY_RETRY_DELAY / 1000}s...`);
                 await delay(EMPTY_RETRY_DELAY);
                 if (!isDeleting) { log("Deletion stopped during empty wait.", "warn"); break; }
                 currentElements = getCurrentSongElements();
                 currentSize = currentElements.length;
                 log(`Re-checking after delay... Found ${currentSize} songs.`);
                 if (currentSize > 0) {
                     log("Songs found after wait. Continuing deletion.");
                     updateStatusMessage(`Found ${currentSize} songs after wait. Resuming...`);
                     emptyChecks = 0;
                 } else {
                     emptyChecks++;
                     log(`Still empty (Check ${emptyChecks}/${MAX_EMPTY_CHECKS}).`);
                     updateStatusMessage(`Still empty (Check ${emptyChecks}/${MAX_EMPTY_CHECKS}).`);
                     if (emptyChecks >= MAX_EMPTY_CHECKS) {
                         log("No songs found after multiple retries. Assuming library is empty or cannot load more.");
                         updateStatusMessage("Library appears empty after retries.");
                         isDeleting = false;
                         break;
                     }
                     continue;
                 }
             }
             emptyChecks = 0;
             log(`Processing batch of ${currentSize} songs...`);
             let batchDeleted = 0;
             while (currentSize > 0 && isDeleting) {
                 if (!isDeleting) { log("Deletion stopped during batch processing.", "warn"); break; }
                 const firstElement = getCurrentSongElements()[0];
                 if (!firstElement || !firstElement.parentNode) {
                     log(`Top song element disappeared unexpectedly. Re-evaluating list...`, "warn");
                     await delay(100);
                     currentSize = getCurrentSongElements().length;
                     continue;
                 }
                 const titleH4 = firstElement.querySelector('h4.text-primary');
                 const title = titleH4 ? titleH4.textContent.trim() : `Top song`;
                 const deletionIdentifier = `Bulk ${totalDeleted + batchDeleted + 1}`;
                 log(`Deleting: ${title} (${deletionIdentifier})...`);
                 updateStatusMessage(`Deleting ${title} (${totalDeleted + batchDeleted + 1} total...)`);
                 const success = await processSingleDeletion(firstElement, deletionIdentifier);
                 if (success) {
                     batchDeleted++;
                     await delay(50);
                     currentSize = getCurrentSongElements().length;
                 } else {
                     log(`Failed to delete ${title}. Stopping bulk delete.`, "error");
                     updateStatusMessage(`Error deleting ${title}. Stopped.`);
                     isDeleting = false;
                     break;
                 }
                 await delay(50);
             }
             totalDeleted += batchDeleted;
             if (isDeleting) {
                 log(`Batch attempt complete. Deleted ${batchDeleted} this round. Total: ${totalDeleted}. Checking for more...`);
                 updateStatusMessage(`Batch complete. Total: ${totalDeleted}. Checking for more...`);
             }
         }
         const finalReason = !isDeleting && emptyChecks < MAX_EMPTY_CHECKS ? 'INTERRUPTED' : 'COMPLETE';
         log(`--- LIBRARY DELETION ${finalReason} (Bulk Mode) --- Total deleted: ${totalDeleted}`);
         updateStatusMessage(finalReason === 'INTERRUPTED' ? `Deletion stopped. Total: ${totalDeleted}` : `Deletion complete! Total: ${totalDeleted}`);
         isDeleting = false;
         setButtonsDisabled(false);
     }
    async function processSingleDeletion(songElement, identifier, retryCount = 0) {
        const logPrefix = `(ID/Index: ${identifier}) -`;
        if (!songElement || !songElement.parentNode) {
            log(`${logPrefix} Song element is already gone. Assuming success.`, "warn");
            return true;
        }
        const menuButton = songElement.querySelector('button[aria-label^="More options for"]');
        if (!menuButton) {
            log(`${logPrefix} 'More options' button not found. Cannot proceed.`, "error");
            logDebug(`${logPrefix} Searched within element:`, songElement);
            return false;
        }
        logDebug(`${logPrefix} Clicking 'More options' button:`, menuButton);
        if (!simulateClick(menuButton)) {
            log(`${logPrefix} Failed to simulate click on 'More options'.`, "error");
            return false;
        }
        await delay(DROPDOWN_DELAY);
        const potentialItems = document.querySelectorAll('[role="menuitem"], div[role="option"], button');
        logDebug(`${logPrefix} Found ${potentialItems.length} potential menu items after clicking options.`);
        const deleteOption = Array.from(potentialItems).find(el =>
            el.textContent.trim().toLowerCase() === 'delete' &&
            el.offsetParent !== null &&
            el.closest('[role="menu"], [role="listbox"]')
        );
        if (!deleteOption && retryCount < MAX_RETRIES) {
            log(`${logPrefix} Delete option not found (Attempt ${retryCount + 1}/${MAX_RETRIES}). Retrying click sequence...`, "warn");
            try { document.body.click(); await delay(100); } catch(e){}
            if (!songElement || !songElement.parentNode) {
                log(`${logPrefix} Song element disappeared before retry could occur.`, "warn");
                return true;
            }
            return processSingleDeletion(songElement, identifier, retryCount + 1);
        }
        if (!deleteOption) {
            log(`${logPrefix} 'Delete' option not found after ${MAX_RETRIES} retries. Aborting deletion for this song.`, "error");
            try { document.body.click(); } catch(e){}
            return false;
        }
        logDebug(`${logPrefix} Clicking 'Delete' option:`, deleteOption);
        if (!simulateClick(deleteOption)) {
            log(`${logPrefix} Failed to simulate click on 'Delete' option.`, "error");
            return false;
        }
        await delay(DELETION_DELAY);
        logDebug(`--- Finished processing ${logPrefix} (Assumed Success after Delete Click) ---`);
        return true;
    }

    // --- Utility ---
    function setButtonsDisabled(disabled) {
        if (!isMinimized || !disabled) {
            document.querySelectorAll('#riffDelControlUI button').forEach(btn => {
                if (btn.id !== 'minimizeButton') {
                    btn.disabled = disabled;
                }
            });
             const ignoreLiked = document.getElementById('ignoreLikedToggle'); if (ignoreLiked) ignoreLiked.disabled = disabled;
             const keywordInput = document.getElementById('keywordFilterInput'); if (keywordInput) keywordInput.disabled = disabled;
             const selectAll = document.getElementById('selectAll'); if (selectAll) selectAll.disabled = disabled;
             document.querySelectorAll('#songList input[type="checkbox"]').forEach(cb => cb.disabled = disabled);
        }
         const status = document.getElementById('statusMessage'); if (status) status.style.pointerEvents = disabled ? 'none' : 'auto';
    }

    // --- Initialization ---
    function waitForPageLoad(callback) {
        if (document.readyState === "complete" || document.readyState === "interactive") {
            setTimeout(callback, 500);
        } else {
            window.addEventListener('load', () => { setTimeout(callback, 500); }, { once: true });
        }
    }
    function init() {
        if (window.location.pathname.includes('/library/my-songs')) {
            try {
                // Use GM_info to access metadata like version
                log(`Riffusion Song Deletion Automation Script Loaded (v${GM_info.script.version}).`);
                createMainUI();
                log(`Initialized in ${currentMode} mode. UI is ${isMinimized ? 'minimized (Top-Right)' : 'visible'}.`);
            } catch (e) {
                console.error("[RiffDel] Initialization failed:", e);
                alert("[RiffDel] Failed to initialize script. See console for errors.");
            }
        } else {
            logDebug("Not on the target /library/my-songs page.");
        }
    }

    waitForPageLoad(init);

})();