Riffusion Multitool

Adds robust song deletion (Selective & Bulk) and a Download Queue tool with multi-format selection and delay. Features: Main Menu, Independent Lists, Keyword Filters, Liked Filters/Selectors, Draggable & Minimizable UI. USE WITH CAUTION.

目前為 2025-05-01 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Riffusion Multitool
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  Adds robust song deletion (Selective & Bulk) and a Download Queue tool with multi-format selection and delay. Features: Main Menu, Independent Lists, Keyword Filters, Liked Filters/Selectors, Draggable & Minimizable UI. 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_VIEW = 'menu';
    const DELETION_DELAY = 500;
    const DOWNLOAD_MENU_DELAY = 450;
    const DOWNLOAD_ACTION_DELAY = 500;
    const DEFAULT_INTRA_FORMAT_DELAY_SECONDS = 6;
    const DROPDOWN_DELAY = 300;
    const DEFAULT_INTER_SONG_DELAY_SECONDS = 6;
    const MAX_RETRIES = 3;
    const MAX_EMPTY_CHECKS = 3;
    const EMPTY_RETRY_DELAY = 6000;
    const KEYWORD_FILTER_DEBOUNCE = 500;
    const UI_INITIAL_TOP = '60px';
    const UI_INITIAL_RIGHT = '20px';
    const INITIAL_IGNORE_LIKED_DELETE = true;
    const MINIMIZED_ICON_SIZE = '40px';
    const MINIMIZED_ICON_TOP = '15px';
    const MINIMIZED_ICON_RIGHT = '15px';

    // --- State Variables ---
    let debugMode = false;
    let isDeleting = false;
    let isDownloading = false;
    let currentView = INITIAL_VIEW;
    let ignoreLikedSongsDeleteState = INITIAL_IGNORE_LIKED_DELETE;
    let downloadInterSongDelaySeconds = DEFAULT_INTER_SONG_DELAY_SECONDS;
    let downloadIntraFormatDelaySeconds = DEFAULT_INTRA_FORMAT_DELAY_SECONDS;
    let keywordFilterDebounceTimer = null;

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

    // --- Styling (Compact Adjustments) ---
    GM_addStyle(`
        #riffControlUI {
            position: fixed; background: linear-gradient(145deg, #2a2a2a, #1e1e1e); border: 1px solid #444; border-radius: 10px; padding: 0; z-index: 10000; width: 300px; /* Slightly narrower */ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.4); color: #e0e0e0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; user-select: none; overflow: hidden;
            display: ${isMinimized ? 'none' : 'block'};
        }
        #riffControlHeader {
            background: linear-gradient(90deg, #3a3a3a, #2c2c2c); padding: 8px 12px; /* Reduced padding */ cursor: move; border-bottom: 1px solid #444; border-radius: 10px 10px 0 0; position: relative;
        }
        #riffControlHeader h3 { margin: 0; font-size: 15px; /* Slightly smaller */ font-weight: 600; color: #ffffff; text-align: center; text-shadow: 0 1px 1px rgba(0,0,0,0.2); padding-right: 25px; }

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

        #riffControlMinimizedIcon {
            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;
        }
        #riffControlMinimizedIcon:hover { background: linear-gradient(145deg, #4a4a4a, #3c3c3c); }

        #riffControlContent { padding: 12px; /* Reduced padding */ }
        .riffControlButton { display: block; border: none; border-radius: 6px; /* Slightly smaller radius */ padding: 8px; /* Reduced padding */ font-size: 13px; /* Slightly smaller */ font-weight: 500; text-align: center; cursor: pointer; transition: transform 0.15s, background 0.15s; width: 100%; margin-bottom: 8px; /* Reduced margin */ }
        .riffControlButton:hover:not(:disabled) { transform: translateY(-1px); } /* Less hover effect */
        .riffControlButton:disabled { background: #555 !important; cursor: not-allowed; transform: none; opacity: 0.7; }
        .riffMenuButton { background: linear-gradient(90deg, #4d94ff, #3385ff); color: #fff; }
        .riffMenuButton:hover:not(:disabled) { background: linear-gradient(90deg, #3385ff, #1a75ff); }
        .riffBackButton { background: linear-gradient(90deg, #888, #666); color: #fff; margin-top: 12px; /* Reduced */ margin-bottom: 0; } /* No bottom margin on back */
        .riffBackButton:hover:not(:disabled) { background: linear-gradient(90deg, #666, #444); }
        #deleteAllButton, #deleteButton { background: linear-gradient(90deg, #ff4d4d, #e63939); color: #fff; }
        #deleteAllButton:hover:not(:disabled), #deleteButton:hover:not(:disabled) { background: linear-gradient(90deg, #e63939, #cc3333); }
        #startDownloadQueueButton { background: linear-gradient(90deg, #1db954, #17a34a); color: #fff; }
        #startDownloadQueueButton:hover:not(:disabled) { background: linear-gradient(90deg, #17a34a, #158a3f); }
        #reloadDeleteButton, #reloadDownloadButton { background: linear-gradient(90deg, #ff9800, #e68a00); color: #fff; }
        #reloadDeleteButton:hover:not(:disabled), #reloadDownloadButton:hover:not(:disabled) { background: linear-gradient(90deg, #e68a00, #cc7a00); }
        #debugToggle { background: linear-gradient(90deg, #6666ff, #4d4dff); color: #fff; margin-top: 10px; /* Space before debug */ }
        #debugToggle:hover:not(:disabled) { background: linear-gradient(90deg, #4d4dff, #3333cc); }

        #statusMessage { margin-top: 8px; /* Reduced margin */ font-size: 12px; /* Slightly smaller */ color: #1db954; text-align: center; min-height: 1.1em; word-wrap: break-word; }
        .section-controls { display: none; }
        .songListContainer { margin-bottom: 10px; /* Reduced margin */ max-height: 22vh; /* Reduced height */ overflow-y: auto; padding-right: 5px; border: 1px solid #444; border-radius: 5px; background-color: rgba(0,0,0,0.1); padding: 6px; /* Reduced padding */ }
        .songListContainer label { display: flex; align-items: center; margin: 6px 0; /* Reduced margin */ color: #d0d0d0; font-size: 13px; /* Slightly smaller */ transition: color 0.2s; }
        .songListContainer label:hover:not(.ignored) { color: #ffffff; }
        .songListContainer input[type="checkbox"] { margin-right: 8px; accent-color: #1db954; width: 15px; height: 15px; /* Slightly smaller */ cursor: pointer; flex-shrink: 0; }
        .songListContainer input[type="checkbox"]:disabled { cursor: not-allowed; accent-color: #555; }
        .songListContainer label.ignored { color: #777; cursor: not-allowed; font-style: italic; }
        .songListContainer label.liked { font-weight: bold; color: #8c8cff; }
        .songListContainer label.liked:hover { color: #a0a0ff; }

        .selectAllContainer { margin-bottom: 8px; /* Reduced margin */ display: flex; align-items: center; color: #d0d0d0; font-size: 13px; /* Slightly smaller */ font-weight: 500; cursor: pointer; }
        .selectAllContainer input[type="checkbox"] { margin-right: 8px; accent-color: #1db954; width: 15px; height: 15px; /* Slightly smaller */ }
        .selectAllContainer:hover { color: #ffffff; }
        .counterDisplay { margin-bottom: 8px; /* Reduced margin */ font-size: 13px; /* Slightly smaller */ color: #1db954; text-align: center; }
        .songListContainer::-webkit-scrollbar { width: 6px; /* Narrower scrollbar */ }
        .songListContainer::-webkit-scrollbar-track { background: #333; border-radius: 3px; }
        .songListContainer::-webkit-scrollbar-thumb { background: #555; border-radius: 3px; }
        .songListContainer::-webkit-scrollbar-thumb:hover { background: #777; }

        .filterSettings { margin-top: 8px; margin-bottom: 8px; padding-top: 8px; border-top: 1px solid #444; }
        .filterSettings label, .downloadFormatContainer label, .downloadDelayContainer label { display: flex; align-items: center; font-size: 12px; /* Slightly smaller */ color: #ccc; cursor: pointer; margin-bottom: 6px; /* Reduced margin */ }
        .filterSettings label:hover, .downloadFormatContainer label:hover, .downloadDelayContainer label:hover { color: #fff; }
        .filterSettings input[type="checkbox"], .downloadFormatContainer input[type="checkbox"] { margin-right: 6px; accent-color: #1db954; width: 14px; height: 14px; cursor: pointer; }
        .filterSettings input[type="text"], .filterSettings input[type="number"] { width: 100%; background-color: #333; border: 1px solid #555; color: #ddd; padding: 5px 8px; /* Reduced padding */ border-radius: 5px; font-size: 12px; /* Slightly smaller */ box-sizing: border-box; margin-top: 4px; /* Reduced margin */ }
        .filterSettings input[type="text"]:focus, .filterSettings input[type="number"]:focus { outline: none; border-color: #777; }

        #downloadSelectLiked { background: linear-gradient(90deg, #6666ff, #4d4dff); color: #fff; }
        #downloadSelectLiked:hover:not(:disabled) { background: linear-gradient(90deg, #4d4dff, #3333cc); }
        .downloadButtonRow { display: flex; align-items: center; gap: 6px; /* Reduced gap */ margin-bottom: 8px; /* Reduced margin */ }
        #downloadSelectLiked { flex-grow: 1; }
        #downloadClearSelection {
             background: linear-gradient(90deg, #ff4d4d, #e63939); color: #fff;
             width: 28px; height: 28px; /* Slightly smaller */ padding: 0; font-size: 15px; font-weight: bold; line-height: 1; border: none; border-radius: 5px; cursor: pointer; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; margin-bottom: 0; transition: transform 0.15s, background 0.15s;
        }
         #downloadClearSelection:hover:not(:disabled) { background: linear-gradient(90deg, #e63939, #cc3333); transform: translateY(-1px); }
         #downloadClearSelection:disabled { background: #555 !important; cursor: not-allowed; transform: none; opacity: 0.7; }

         .downloadFormatContainer { margin-top: 8px; padding-top: 8px; border-top: 1px solid #444; }
         .downloadFormatContainer > label { margin-bottom: 4px; /* Reduced margin */ justify-content: center; display: block; text-align: center;}
         .downloadFormatContainer div { display: flex; justify-content: space-around; margin-top: 4px; }
         .downloadFormatContainer label { margin-bottom: 0; } /* Ensure format labels themselves have no extra bottom margin */

         .downloadDelayContainer { margin-top: 8px; padding-top: 8px; border-top: 1px solid #444; display: flex; justify-content: space-between; gap: 10px; /* Reduced gap */ }
         .downloadDelayContainer > div { flex: 1; }
         .downloadDelayContainer label { margin-bottom: 2px; display: block; }
         .downloadDelayContainer input[type="number"] { margin-top: 0; }

         /* Bulk delete description */
         #bulkModeControls p { font-size: 11px; color:#aaa; text-align:center; margin-top:4px; margin-bottom: 8px; }
    `);

    // --- 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="[RiffTool]"; if(l==='error') console.error(`${p} ${m}`); else if(l==='warn') console.warn(`${p} ${m}`); else console.log(`${p} ${m}`); updateStatusMessage(m); }
    function logDebug(m, e=null) { if(!debugMode) return; console.log(`[RiffTool DEBUG] ${m}`, e instanceof Element ? e.outerHTML.substring(0,250)+'...' : e !== null ? e : ''); }
    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('[RiffTool] 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 = 'riffControlUI';
        // Position calculation remains the same
        if (UI_INITIAL_RIGHT) {
            const rightPx = parseInt(UI_INITIAL_RIGHT, 10);
            const widthPx = 300; // Match style width
            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="riffControlHeader">
                <h3>Riffusion Multitool v${GM_info.script.version}</h3>
                <button id="minimizeButton" title="Minimize UI">_</button>
            </div>
            <div id="riffControlContent">

                <!-- Main Menu -->
                <div id="mainMenuControls" class="section-controls">
                    <button id="goToSelectiveDelete" class="riffMenuButton riffControlButton">Selective Deletion</button> <!-- Shortened -->
                    <button id="goToBulkDelete" class="riffMenuButton riffControlButton">Bulk Deletion</button> <!-- Shortened -->
                    <button id="goToDownloadQueue" class="riffMenuButton riffControlButton">Download Queue</button> <!-- Shortened -->
                </div>

                <!-- Selective Deletion -->
                <div id="selectiveModeControls" class="section-controls">
                    <button class="riffBackButton riffControlButton backToMenuButton">Back to Menu</button> <!-- Shortened -->
                    <label class="selectAllContainer"><input type="checkbox" id="deleteSelectAll"> Select All Visible</label> <!-- Shortened -->
                    <div id="deleteSongList" class="songListContainer">Loading...</div>
                    <div id="deleteCounter" class="counterDisplay">Deleted: 0 / 0</div>
                    <button id="deleteButton" class="riffControlButton">Delete Selected</button>
                    <button id="reloadDeleteButton" class="riffControlButton">Reload List</button> <!-- Shortened -->
                    <div class="filterSettings">
                        <label><input type="checkbox" id="ignoreLikedToggleDelete"> Ignore Liked</label> <!-- Shortened -->
                        <input type="text" id="deleteKeywordFilterInput" placeholder="Keywords to ignore (comma-sep)...">
                    </div>
                </div>

                <!-- Bulk Deletion -->
                <div id="bulkModeControls" class="section-controls">
                     <button class="riffBackButton riffControlButton backToMenuButton">Back to Menu</button>
                    <button id="deleteAllButton" class="riffControlButton">Delete Entire Library</button>
                    <p>Deletes all songs without scrolling. Retries if needed.</p> <!-- Shortened -->
                </div>

                <!-- Download Queue -->
                <div id="downloadQueueControls" class="section-controls">
                    <button class="riffBackButton riffControlButton backToMenuButton">Back to Menu</button>
                    <label class="selectAllContainer"><input type="checkbox" id="downloadSelectAll"> Select All</label>
                    <div class="downloadButtonRow">
                        <button id="downloadSelectLiked" class="riffControlButton">Select/Deselect Liked</button> <!-- Shortened -->
                        <button id="downloadClearSelection" title="Clear Selection" class="riffControlButton">C</button>
                    </div>
                    <div id="downloadSongList" class="songListContainer">Loading...</div>
                     <div id="downloadCounter" class="counterDisplay">Downloaded: 0 / 0</div>
                    <button id="startDownloadQueueButton" class="riffControlButton">Start Download Queue</button>
                    <button id="reloadDownloadButton" class="riffControlButton">Reload List</button> <!-- Shortened -->
                    <div class="filterSettings">
                        <label for="downloadKeywordFilterInput">Filter list by keywords:</label>
                        <input type="text" id="downloadKeywordFilterInput" placeholder="Keywords to show (comma-sep)...">

                        <div class="downloadFormatContainer">
                            <label>Download Formats:</label>
                            <div>
                                <label><input type="checkbox" id="formatMP3" value="MP3" checked> MP3</label>
                                <label><input type="checkbox" id="formatM4A" value="M4A"> M4A</label>
                                <label><input type="checkbox" id="formatWAV" value="WAV"> WAV</label>
                            </div>
                        </div>

                        <div class="downloadDelayContainer">
                             <div>
                                 <label for="downloadIntraFormatDelayInput">Format Delay (s):</label> <!-- Shortened -->
                                 <input type="number" id="downloadIntraFormatDelayInput" min="0" step="0.1" value="${DEFAULT_INTRA_FORMAT_DELAY_SECONDS}">
                             </div>
                             <div>
                                 <label for="downloadInterSongDelayInput">Song Delay (s):</label> <!-- Shortened -->
                                 <input type="number" id="downloadInterSongDelayInput" min="1" value="${DEFAULT_INTER_SONG_DELAY_SECONDS}">
                             </div>
                        </div>
                    </div>
                </div>

                <!-- Footer -->
                <button id="debugToggle" class="riffControlButton">${debugMode?'Disable Debug':'Enable Debug'}</button>
                <div id="statusMessage">Ready.</div>
            </div>`;
        document.body.appendChild(uiElement);

        minimizedIconElement = document.createElement('div');
        minimizedIconElement.id = 'riffControlMinimizedIcon';
        minimizedIconElement.textContent = 'RM';
        minimizedIconElement.title = 'Restore Riffusion Multitool';
        minimizedIconElement.style.display = isMinimized ? 'flex' : 'none';
        document.body.appendChild(minimizedIconElement);

        // --- Event Listeners ---
        const header = uiElement.querySelector('#riffControlHeader');
        enableDrag(uiElement, header);
        document.getElementById('minimizeButton')?.addEventListener('click', minimizeUI);
        minimizedIconElement?.addEventListener('click', restoreUI);

        // Menu Navigation
        document.getElementById('goToSelectiveDelete')?.addEventListener('click', () => navigateToView('selective'));
        document.getElementById('goToBulkDelete')?.addEventListener('click', () => navigateToView('bulk'));
        document.getElementById('goToDownloadQueue')?.addEventListener('click', () => navigateToView('download'));
        uiElement.querySelectorAll('.backToMenuButton').forEach(btn => btn.addEventListener('click', () => navigateToView('menu')));

        // Selective Delete Controls
        document.getElementById('deleteSelectAll')?.addEventListener('change', (e) => toggleSelectAll(e, '#deleteSongList'));
        document.getElementById('deleteButton')?.addEventListener('click', deleteSelectedSongs);
        document.getElementById('reloadDeleteButton')?.addEventListener('click', () => { if (currentView === 'selective') populateDeleteSongList(); });
        const ignoreLikedToggle = document.getElementById('ignoreLikedToggleDelete');
        if (ignoreLikedToggle) { ignoreLikedToggle.checked = ignoreLikedSongsDeleteState; ignoreLikedToggle.addEventListener('change', (e) => { ignoreLikedSongsDeleteState = e.target.checked; log(`Ignore Liked Songs (Delete): ${ignoreLikedSongsDeleteState}`); populateDeleteSongList(); });}
        const deleteKeywordInput = document.getElementById('deleteKeywordFilterInput');
        if (deleteKeywordInput) { deleteKeywordInput.addEventListener('input', debounce(() => { log('Delete keywords changed, refreshing list...'); populateDeleteSongList(); }, KEYWORD_FILTER_DEBOUNCE)); }

        // Bulk Delete Controls
        document.getElementById('deleteAllButton')?.addEventListener('click', () => {if (isDeleting || isDownloading) { log("Operation already in progress.", "warn"); return; } if (confirm("ARE YOU SURE? This will attempt to delete ALL songs in your library without scrolling. NO UNDO.")) { deleteAllSongsInLibrary(); }});

        // Download Queue Controls
        document.getElementById('downloadSelectAll')?.addEventListener('change', (e) => toggleSelectAll(e, '#downloadSongList'));
        document.getElementById('downloadSelectLiked')?.addEventListener('click', toggleSelectLiked);
        document.getElementById('downloadClearSelection')?.addEventListener('click', clearDownloadSelection);
        document.getElementById('startDownloadQueueButton')?.addEventListener('click', startDownloadQueue);
        document.getElementById('reloadDownloadButton')?.addEventListener('click', () => { if (currentView === 'download') populateDownloadSongList(); });
        const downloadKeywordInput = document.getElementById('downloadKeywordFilterInput');
        if (downloadKeywordInput) { downloadKeywordInput.addEventListener('input', debounce(() => { log('Download filter changed, refreshing list...'); populateDownloadSongList(); }, KEYWORD_FILTER_DEBOUNCE)); }
        const interSongDelayInput = document.getElementById('downloadInterSongDelayInput');
        if (interSongDelayInput) { interSongDelayInput.value = downloadInterSongDelaySeconds; interSongDelayInput.addEventListener('input', (e) => { const val = parseInt(e.target.value, 10); if (!isNaN(val) && val >= 0) { downloadInterSongDelaySeconds = val; log(`Inter-Song delay set to: ${downloadInterSongDelaySeconds}s`); } }); }
        const intraFormatDelayInput = document.getElementById('downloadIntraFormatDelayInput');
        if (intraFormatDelayInput) { intraFormatDelayInput.value = downloadIntraFormatDelaySeconds; intraFormatDelayInput.addEventListener('input', (e) => { const val = parseFloat(e.target.value); if (!isNaN(val) && val >= 0) { downloadIntraFormatDelaySeconds = val; log(`Intra-Format delay set to: ${downloadIntraFormatDelaySeconds}s`); } }); }


        // Global Controls
        document.getElementById('debugToggle').addEventListener('click', toggleDebug);

        updateUIVisibility();
    }

    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");
    }

    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 });
        updateUIVisibility();
    }

    function navigateToView(view) {
        if (isDeleting || isDownloading) {
            log("Cannot switch views while an operation is in progress.", "warn");
            return;
        }
        logDebug(`Navigating to view: ${view}`);
        currentView = view;
        updateUIVisibility();
    }

    function updateUIVisibility() {
        if (isMinimized || !uiElement) return;

        const sections = {
            menu: document.getElementById('mainMenuControls'),
            selective: document.getElementById('selectiveModeControls'),
            bulk: document.getElementById('bulkModeControls'),
            download: document.getElementById('downloadQueueControls')
        };
        const headerTitle = uiElement.querySelector('#riffControlHeader h3');
        const statusMsg = document.getElementById('statusMessage');

        let title = `Riffusion Multitool v${GM_info.script.version}`;

        Object.values(sections).forEach(section => {
            if (section) section.style.display = 'none';
        });

        if (sections[currentView]) {
            sections[currentView].style.display = 'block';
            switch (currentView) {
                case 'menu':
                    title += " - Menu";
                    updateStatusMessage("Select a tool.");
                     break;
                case 'selective':
                    title += " - Selective Deletion";
                    populateDeleteSongListIfNeeded();
                    updateStatusMessage("Select songs to delete.");
                    break;
                case 'bulk':
                    title += " - Bulk Deletion";
                     updateStatusMessage("Warning: Deletes entire library.");
                    break;
                case 'download':
                    title += " - Download Queue";
                    populateDownloadSongListIfNeeded();
                    updateStatusMessage("Select songs to download.");
                    break;
            }
        } else {
            log(`View '${currentView}' not found, showing menu.`, 'warn');
            sections.menu.style.display = 'block';
            currentView = 'menu';
            title += " - Menu";
             updateStatusMessage("Select a tool.");
        }

        if (headerTitle) headerTitle.textContent = title;
        if (statusMsg) statusMsg.style.display = 'block';

        logDebug(`UI Visibility Updated. Current View: ${currentView}`);
    }


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

    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 });
             }
        }
    }

    // --- Song List Population & Filtering ---

    function getSongDataFromPage() {
        const songElements = getCurrentSongElements();
        logDebug(`Found ${songElements.length} song elements on page.`);
        const songs = [];
        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}`;
             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 likedIcon = songElement.querySelector('svg[data-prefix="fas"][data-icon="heart"]');
             const isLiked = likedIcon !== null;

             songs.push({
                 id: songId,
                 title: title,
                 titleLower: title.toLowerCase(),
                 isLiked: isLiked,
                 element: songElement
             });
        });
        return songs;
    }

    function populateDeleteSongListIfNeeded() {
        const songListDiv = document.getElementById('deleteSongList');
        if (!songListDiv) return;
        if (songListDiv.innerHTML === '' || songListDiv.innerHTML === 'Loading...' || songListDiv.children.length === 0) {
            populateDeleteSongList();
        }
    }
    function populateDeleteSongList() {
         if (currentView !== 'selective' || isMinimized) return;
         logDebug('Populating DELETE song list with filters...');
         const songListDiv = document.getElementById('deleteSongList');
         const deleteCounter = document.getElementById('deleteCounter');
         if (!songListDiv) return;
         songListDiv.innerHTML = 'Loading...';
         if(deleteCounter) deleteCounter.textContent = 'Deleted: 0 / 0';
         const selectAllCheckbox = document.getElementById('deleteSelectAll');
         if (selectAllCheckbox) selectAllCheckbox.checked = false;
         const ignoreLikedCheckbox = document.getElementById('ignoreLikedToggleDelete');
         if(ignoreLikedCheckbox) ignoreLikedCheckbox.checked = ignoreLikedSongsDeleteState;

         const keywordInput = document.getElementById('deleteKeywordFilterInput');
         const keywordString = keywordInput ? keywordInput.value : '';
         const dynamicIgnoreKeywords = keywordString.split(',').map(k => k.trim().toLowerCase()).filter(k => k !== '');
         logDebug(`Keywords to ignore for delete: [${dynamicIgnoreKeywords.join(', ')}]`);

         setTimeout(() => {
             const songs = getSongDataFromPage();
             songListDiv.innerHTML = '';

             if (songs.length === 0) {
                 songListDiv.innerHTML = '<p style="color:#d0d0d0;text-align:center;font-size:12px;">No songs found.</p>'; // Adjusted message
                 updateStatusMessage("No songs found.");
                 return;
             }

             let ignoredCount = 0;
             let visibleCount = 0;
             songs.forEach(song => {
                 const keywordMatch = dynamicIgnoreKeywords.length > 0 && dynamicIgnoreKeywords.some(keyword => song.titleLower.includes(keyword));
                 const likedMatch = ignoreLikedSongsDeleteState && song.isLiked;
                 const shouldIgnore = keywordMatch || likedMatch;

                 let ignoreReason = '';
                 if (keywordMatch) ignoreReason += 'Keyword';
                 if (likedMatch) ignoreReason += (keywordMatch ? ' & Liked' : 'Liked');

                 const label = document.createElement('label');
                 label.innerHTML = `<input type="checkbox" data-song-id="${song.id}" ${shouldIgnore ? 'disabled' : ''}> ${song.title}`;
                 if (song.isLiked) label.classList.add('liked');

                 if (shouldIgnore) {
                    label.classList.add('ignored');
                    label.title = `Ignoring for delete: ${ignoreReason}`;
                    ignoredCount++;
                 } else {
                    visibleCount++;
                 }
                 songListDiv.appendChild(label);
             });

             logDebug(`Populated DELETE list: ${songs.length} total, ${visibleCount} selectable, ${ignoredCount} ignored.`);
             updateStatusMessage(`Loaded ${songs.length} songs (${ignoredCount} ignored).`); // Simplified
         }, 100);
     }

    function populateDownloadSongListIfNeeded() {
        const songListDiv = document.getElementById('downloadSongList');
         if (!songListDiv) return;
        if (songListDiv.innerHTML === '' || songListDiv.innerHTML === 'Loading...' || songListDiv.children.length === 0) {
            populateDownloadSongList();
        }
    }
    function populateDownloadSongList() {
        if (currentView !== 'download' || isMinimized) return;
        logDebug('Populating DOWNLOAD song list with filters...');
        const songListDiv = document.getElementById('downloadSongList');
        const downloadCounter = document.getElementById('downloadCounter');
        if (!songListDiv) return;
        songListDiv.innerHTML = 'Loading...';
        if(downloadCounter) downloadCounter.textContent = 'Downloaded: 0 / 0';
        const selectAllCheckbox = document.getElementById('downloadSelectAll');
        if (selectAllCheckbox) selectAllCheckbox.checked = false;

        const keywordInput = document.getElementById('downloadKeywordFilterInput');
        const keywordString = keywordInput ? keywordInput.value : '';
        const filterKeywords = keywordString.split(',').map(k => k.trim().toLowerCase()).filter(k => k !== '');
        logDebug(`Keywords to filter for download: [${filterKeywords.join(', ')}]`);

        setTimeout(() => {
            const songs = getSongDataFromPage();
            songListDiv.innerHTML = '';

             if (songs.length === 0) {
                 songListDiv.innerHTML = '<p style="color:#d0d0d0;text-align:center;font-size:12px;">No songs found.</p>'; // Adjusted message
                 updateStatusMessage("No songs found.");
                 updateSelectLikedButtonText();
                 return;
             }

            let displayedCount = 0;
            songs.forEach(song => {
                const keywordMatch = filterKeywords.length === 0 || filterKeywords.some(keyword => song.titleLower.includes(keyword));

                if (keywordMatch) {
                    const label = document.createElement('label');
                    label.innerHTML = `<input type="checkbox" data-song-id="${song.id}" data-is-liked="${song.isLiked}"> ${song.title}`;
                    if (song.isLiked) {
                        label.classList.add('liked');
                    }
                    songListDiv.appendChild(label);
                    displayedCount++;
                } else {
                    logDebug(`Filtering out song for download view ${song.id} ('${song.title}') due to keywords.`);
                }
            });

            logDebug(`Populated DOWNLOAD list: ${songs.length} total, ${displayedCount} displayed after filtering.`);
            updateStatusMessage(`Showing ${displayedCount} of ${songs.length} songs.`); // Simplified
            updateSelectLikedButtonText();
        }, 100);
    }


    function toggleSelectAll(event, listSelector) {
        if (isMinimized) return;
        const isChecked = event.target.checked;
        const checkboxes = document.querySelectorAll(`${listSelector} input[type="checkbox"]:not(:disabled)`);
        checkboxes.forEach(cb => cb.checked = isChecked);
        logDebug(`Select All Toggled in ${listSelector}: ${isChecked} (${checkboxes.length} items affected)`);
        if(listSelector === '#downloadSongList') {
            updateSelectLikedButtonText();
        }
    }

    function updateSelectLikedButtonText() {
        if (currentView !== 'download' || isMinimized) return;
        const button = document.getElementById('downloadSelectLiked');
        if (!button) return;

        const checkboxes = document.querySelectorAll('#downloadSongList input[type="checkbox"]:not(:disabled)');
        if (checkboxes.length === 0) {
             button.textContent = 'Select Liked'; // Simpler text when none available
             return;
        }

        let shouldSelect = false;
        checkboxes.forEach(cb => {
            if (cb.dataset.isLiked === 'true' && !cb.checked) {
                shouldSelect = true;
            }
        });

        button.textContent = shouldSelect ? 'Select Liked' : 'Deselect Liked'; // Simpler text
    }


    function toggleSelectLiked() {
        if (currentView !== 'download' || isMinimized) return;
        const checkboxes = document.querySelectorAll('#downloadSongList input[type="checkbox"]:not(:disabled)');
        if (checkboxes.length === 0) {
            log("No songs available to select.", "warn");
            return;
        }

        let shouldSelect = false;
        checkboxes.forEach(cb => {
            if (cb.dataset.isLiked === 'true' && !cb.checked) {
                shouldSelect = true;
            }
        });

        let changedCount = 0;
        checkboxes.forEach(cb => {
            if (cb.dataset.isLiked === 'true') {
                 if (cb.checked !== shouldSelect) {
                     cb.checked = shouldSelect;
                     changedCount++;
                 }
            }
        });

        log(`Toggled selection for ${changedCount} liked songs. Action: ${shouldSelect ? 'Select' : 'Deselect'}`);
        updateStatusMessage(`${shouldSelect ? 'Selected' : 'Deselected'} ${changedCount} liked songs.`);

        const selectAllCheckbox = document.getElementById('downloadSelectAll');
        if (selectAllCheckbox) {
            const allVisibleCheckboxes = document.querySelectorAll('#downloadSongList input[type="checkbox"]:not(:disabled)');
            const allVisibleChecked = document.querySelectorAll('#downloadSongList input[type="checkbox"]:not(:disabled):checked');
            selectAllCheckbox.checked = allVisibleCheckboxes.length > 0 && allVisibleCheckboxes.length === allVisibleChecked.length;
        }
        updateSelectLikedButtonText();
    }

    function clearDownloadSelection() {
        if (currentView !== 'download' || isMinimized) return;
        const checkboxes = document.querySelectorAll('#downloadSongList input[type="checkbox"]:checked');
        if (checkboxes.length === 0) {
            log("No songs currently selected.", "info");
            return;
        }
        checkboxes.forEach(cb => cb.checked = false);
        const selectAllCheckbox = document.getElementById('downloadSelectAll');
        if (selectAllCheckbox) selectAllCheckbox.checked = false;
        log(`Cleared selection for ${checkboxes.length} songs.`);
        updateStatusMessage("Selection cleared.");
        updateSelectLikedButtonText();
    }


    function updateCounter(type, count, total) {
       if (isMinimized) return;
       let counterElementId = '';
       if (type === 'delete') counterElementId = 'deleteCounter';
       else if (type === 'download') counterElementId = 'downloadCounter';
       else return;

       const counterElement = document.getElementById(counterElementId);
       if (counterElement) {
           const prefix = type === 'delete' ? 'Deleted' : 'Downloaded';
           counterElement.textContent = `${prefix}: ${count} / ${total}`;
       }
       logDebug(`${type} 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 (currentView !== 'selective') { log("Selective delete only available in Selective View.", "warn"); return; }
        if (isDeleting || isDownloading) { log("Another operation is already in progress.", "warn"); return; }

        const checkboxes = document.querySelectorAll('#deleteSongList input[type="checkbox"]:checked:not(:disabled)');
        const totalToDelete = checkboxes.length;

        if (totalToDelete === 0) {
            updateCounter('delete', 0, 0);
            log('No valid songs selected for deletion.');
            updateStatusMessage('No songs selected or all selected are ignored.');
            return;
        }

        isDeleting = true;
        setAllButtonsDisabled(true);
        const songIdsToDelete = Array.from(checkboxes).map(cb => cb.dataset.songId);
        log(`Starting deletion for ${totalToDelete} selected song IDs: [${songIdsToDelete.join(', ')}]`);
        updateCounter('delete', 0, totalToDelete);
        updateStatusMessage(`Deleting ${totalToDelete} selected...`);

        let deletedCount = 0;
        let criticalErrorOccurred = false;

        for (const songId of songIdsToDelete) {
            logDebug(`Processing Song ID for delete: ${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(`#deleteSongList 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 processSingleAction(songElement, 'delete', songId);
            logDebug(`processSingleAction(delete) result for ID ${songId}: ${success}`);

            if (success) {
                deletedCount++;
                updateCounter('delete', deletedCount, totalToDelete);
                updateStatusMessage(`Deleted ${deletedCount}/${totalToDelete}...`);
                logDebug(`Successfully processed deletion for ID ${songId}. Count: ${deletedCount}`);
                 const checkboxToRemove = document.querySelector(`#deleteSongList 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(`Delete 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;
        setAllButtonsDisabled(false);
    }

     async function deleteAllSongsInLibrary() {
         if (isMinimized) { log("Please restore the UI to delete.", "warn"); return; }
         if (currentView !== 'bulk') { log("Bulk delete only available in Bulk View.", "warn"); return; }
         if (isDeleting || isDownloading) { log("Another operation is already in progress.", "warn"); return; }

         isDeleting = true;
         setAllButtonsDisabled(true);
         log("--- STARTING LIBRARY DELETION (Bulk Mode / No-Scroll) ---");
         updateStatusMessage("Starting full library deletion...");

         let totalDeleted = 0;
         let 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 processSingleAction(firstElement, 'delete', 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;
         setAllButtonsDisabled(false);
     }


    // --- Download Logic ---

    async function startDownloadQueue() {
        if (isMinimized) { log("Please restore the UI to download.", "warn"); return; }
        if (currentView !== 'download') { log("Download only available in Download View.", "warn"); return; }
        if (isDeleting || isDownloading) { log("Another operation is already in progress.", "warn"); return; }

        const checkboxes = document.querySelectorAll('#downloadSongList input[type="checkbox"]:checked:not(:disabled)');
        const totalSongsToDownload = checkboxes.length;

        if (totalSongsToDownload === 0) {
            updateCounter('download', 0, 0);
            log('No valid songs selected for download.');
            updateStatusMessage('No songs selected for download.');
            return;
        }

        const selectedFormats = [];
        if (document.getElementById('formatMP3')?.checked) selectedFormats.push('MP3');
        if (document.getElementById('formatM4A')?.checked) selectedFormats.push('M4A');
        if (document.getElementById('formatWAV')?.checked) selectedFormats.push('WAV');

        if (selectedFormats.length === 0) {
            log('No download formats selected.', 'error');
            updateStatusMessage('Please select at least one download format.');
            return;
        }

        isDownloading = true;
        setAllButtonsDisabled(true);
        const songIdsToDownload = Array.from(checkboxes).map(cb => cb.dataset.songId);
        const interSongDelayMs = downloadInterSongDelaySeconds * 1000;
        const intraFormatDelayMs = downloadIntraFormatDelaySeconds * 1000;

        log(`Starting download queue for ${totalSongsToDownload} songs. Formats: [${selectedFormats.join(', ')}], Inter-Song Delay: ${downloadInterSongDelaySeconds}s, Intra-Format Delay: ${downloadIntraFormatDelaySeconds}s.`);
        updateCounter('download', 0, totalSongsToDownload);
        updateStatusMessage(`Downloading ${totalSongsToDownload} songs (${selectedFormats.join('/')})...`);

        let songsProcessedCount = 0;
        let criticalErrorOccurred = false;

        for (const songId of songIdsToDownload) {
            logDebug(`Processing Song ID for download: ${songId}`);
            if (criticalErrorOccurred || !isDownloading) {
                log(`Stopping download loop. CritErr: ${criticalErrorOccurred}, IsDownloading: ${isDownloading}`, "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. Skipping download for this song.`, "warn");
                continue;
            }

            const titleH4 = songElement.querySelector('h4.text-primary');
            const title = titleH4 ? titleH4.textContent.trim() : `song ID ${songId}`;
            let songDownloadAttempted = false;
            let songDownloadSuccess = false;
            let formatIndex = 0;

            for (const format of selectedFormats) {
                const currentSongElementCheck = document.querySelector(`div[data-sentry-component="DraggableRiffRow"] a[href="/song/${songId}"]`)?.closest('div[data-sentry-component="DraggableRiffRow"]');
                 if (!currentSongElementCheck) {
                      log(`${title} (ID: ${songId}) element disappeared before downloading format ${format}. Skipping remaining formats for this song.`, "warn");
                      break;
                 }

                 logDebug(`Attempting download for ${title} (ID: ${songId}) - Format: ${format}`);
                 updateStatusMessage(`Downloading ${songsProcessedCount + 1}/${totalSongsToDownload}: ${title} (${format})...`);
                 songDownloadAttempted = true;

                 const success = await processSingleAction(currentSongElementCheck, 'download', `${songId}-${format}`, format);
                 logDebug(`processSingleAction(download) result for ID ${songId}, Format ${format}: ${success}`);

                 if (success) {
                     songDownloadSuccess = true;
                     log(`Successfully initiated download for ${title} (${format}).`);
                     formatIndex++;
                     if (formatIndex < selectedFormats.length && isDownloading && intraFormatDelayMs > 0) { // Check delay > 0
                         logDebug(`Waiting ${downloadIntraFormatDelaySeconds}s before next format...`);
                         await delay(intraFormatDelayMs);
                     } else if (isDownloading && intraFormatDelayMs <= 0 && formatIndex < selectedFormats.length) {
                         await delay(50); // Add a minimal delay even if set to 0 to prevent race conditions
                     }
                 } else {
                     log(`Failed to download ${title} (ID: ${songId}) - Format: ${format}. Stopping queue.`, "error");
                     updateStatusMessage(`Error downloading ${title} (${format}). Stopped.`);
                     criticalErrorOccurred = true;
                     break;
                 }
                 if (!isDownloading) {
                     log(`Download stopped externally during format loop for ${songId}.`, "warn");
                     break;
                 }
            } // End inner format loop

            if (songDownloadAttempted) {
                 songsProcessedCount++;
                 if (songDownloadSuccess) {
                     updateCounter('download', songsProcessedCount, totalSongsToDownload);
                 }
            }

            if (criticalErrorOccurred || !isDownloading) {
                 break; // Exit outer song loop
            }

            // Delay *after* processing all formats for one song, before the next song
            if (songsProcessedCount < totalSongsToDownload && isDownloading) {
                 log(`Waiting ${downloadInterSongDelaySeconds}s before next song...`);
                 updateStatusMessage(`Waiting ${downloadInterSongDelaySeconds}s before next song...`);
                 await delay(interSongDelayMs);
            }
        } // End outer song loop

        log(`Download queue finished. Processed ${songsProcessedCount} of ${totalSongsToDownload} selected songs.`);
        updateStatusMessage(criticalErrorOccurred ? `Download stopped due to error. ${songsProcessedCount} songs processed.` : `Download queue complete. ${songsProcessedCount} songs processed.`);
        isDownloading = false;
        setAllButtonsDisabled(false);
    }


    // --- Generic Action Processor (Handles Delete or Download) ---
    async function processSingleAction(songElement, actionType, identifier, format = null, retryCount = 0) {
        const logPrefix = `(${actionType} - ID/Index: ${identifier}) -`;

        if (!songElement || !songElement.parentNode) {
            log(`${logPrefix} Song element is already gone. Assuming success for ${actionType}.`, "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);

        // Find the Primary Action Item ('Delete' or 'Download')
        let primaryActionText = actionType === 'delete' ? 'delete' : 'download';
        let primaryActionItem = null;
        let downloadMenuItemId = null;

        const popperWrapper = document.querySelector(`div[data-radix-popper-content-wrapper][style*="transform: translate"]`);
        let potentialItems = [];
        if (popperWrapper) {
             const menuContent = popperWrapper.querySelector('div[data-radix-menu-content][data-state="open"]');
             if (menuContent) {
                 potentialItems = menuContent.querySelectorAll('[role="menuitem"]');
                 logDebug(`${logPrefix} Found ${potentialItems.length} potential menu items in active popper.`);
             } else { logDebug(`${logPrefix} No open menu content in active popper.`); }
        } else {
             logDebug(`${logPrefix} No active popper wrapper found. Searching globally.`);
             potentialItems = document.querySelectorAll('div[data-radix-popper-content-wrapper] div[data-radix-menu-content][data-state="open"] [role="menuitem"]');
        }

        primaryActionItem = Array.from(potentialItems).find(el => {
            const textContentLower = el.textContent.trim().toLowerCase();
            const isVisible = el.offsetParent !== null;
            if (isVisible && textContentLower === 'download') {
                downloadMenuItemId = el.getAttribute('aria-controls');
                 logDebug(`${logPrefix} Found Download item, controls submenu ID: ${downloadMenuItemId}`);
            }
            return isVisible && textContentLower === primaryActionText;
        });

        // Retry logic
        if (!primaryActionItem && retryCount < MAX_RETRIES) {
            log(`${logPrefix} '${primaryActionText}' option not found (Attempt ${retryCount + 1}/${MAX_RETRIES}). Retrying click sequence...`, "warn");
            try { document.body.click(); await delay(150); } catch(e){}
            if (!songElement || !songElement.parentNode) {
                log(`${logPrefix} Song element disappeared before retry could occur.`, "warn");
                return true;
            }
             const checkMenuButton = songElement.querySelector('button[aria-label^="More options for"]');
             if (!checkMenuButton) {
                  log(`${logPrefix} Song element menu button disappeared before retry could occur.`, "warn");
                  return false;
             }
            return processSingleAction(songElement, actionType, identifier, format, retryCount + 1);
        }

        if (!primaryActionItem) {
            log(`${logPrefix} '${primaryActionText}' option not found after ${MAX_RETRIES} retries. Aborting action.`, "error");
            try { document.body.click(); } catch(e){}
            return false;
        }

        // Click the Primary Action Item
        logDebug(`${logPrefix} Clicking '${primaryActionText}' option (controls: ${downloadMenuItemId || 'N/A'}):`, primaryActionItem);
        if (!simulateClick(primaryActionItem)) {
            log(`${logPrefix} Failed to simulate click on '${primaryActionText}' option.`, "error");
            try { document.body.click(); } catch(e){}
            return false;
        }

        // Handle Subsequent Steps
        if (actionType === 'delete') {
            await delay(DELETION_DELAY);
            logDebug(`--- Finished processing ${logPrefix} (Assumed Success after Delete Click) ---`);
            try { document.body.click(); await delay(50); } catch(e){}
            return true;
        }
        else if (actionType === 'download') {
            if (!downloadMenuItemId) {
                 log(`${logPrefix} Submenu ID for 'Download' was not captured. Aborting download format ${format}.`, "error");
                 try { document.body.click(); } catch(e){}
                 return false;
            }

            await delay(DOWNLOAD_MENU_DELAY);

            // Find the Format Item in the *Specific* Sub-Menu
            let formatItem = null;
            const formatTextUpper = format.toUpperCase();
            const subMenuContent = document.getElementById(downloadMenuItemId);
            let foundInSubMenu = false;

            if (subMenuContent && subMenuContent.getAttribute('data-state') === 'open') {
                foundInSubMenu = true;
                logDebug(`${logPrefix} Found specific sub-menu container (ID: ${downloadMenuItemId}). Searching for format '${formatTextUpper}' within it.`);
                const potentialFormatItems = subMenuContent.querySelectorAll('[role="menuitem"]');
                formatItem = Array.from(potentialFormatItems).find(el => {
                    const textDiv = el.querySelector('.line-clamp-2');
                    const itemText = textDiv ? textDiv.textContent.trim().toUpperCase() : el.textContent.trim().toUpperCase();
                    logDebug(`${logPrefix} Checking potential format item in sub-menu: text='${itemText}', visible=${el.offsetParent !== null}`);
                    return itemText === formatTextUpper && el.offsetParent !== null && !el.querySelector('svg[data-icon="angle-right"]');
                });
            }

            // If not found in specific submenu (or submenu wasn't found open), try delayed check / broader search
             if (!formatItem) {
                 if(foundInSubMenu) {
                     log(`${logPrefix} Format '${formatTextUpper}' not in specific sub-menu (ID: ${downloadMenuItemId}). Checking again after delay...`);
                 } else {
                     log(`${logPrefix} Specific sub-menu (ID: ${downloadMenuItemId}) not found open. Checking again after delay...`);
                 }
                 await delay(250); // Extra delay for submenu appearance
                 const subMenuContentAgain = document.getElementById(downloadMenuItemId);
                  if (subMenuContentAgain && subMenuContentAgain.getAttribute('data-state') === 'open') {
                     const potentialFormatItemsAgain = subMenuContentAgain.querySelectorAll('[role="menuitem"]');
                      formatItem = Array.from(potentialFormatItemsAgain).find(el => {
                         const textDiv = el.querySelector('.line-clamp-2');
                         const itemText = textDiv ? textDiv.textContent.trim().toUpperCase() : el.textContent.trim().toUpperCase();
                         return itemText === formatTextUpper && el.offsetParent !== null && !el.querySelector('svg[data-icon="angle-right"]');
                      });
                      if(formatItem) {
                           logDebug(`${logPrefix} Found format '${formatTextUpper}' in specific sub-menu after delay.`);
                      }
                 }
             }

            // Final check if still not found
            if (!formatItem) {
                 log(`${logPrefix} Format option '${formatTextUpper}' not found after checks. Aborting download format ${format}.`, "error");
                 logDebug(`${logPrefix} Submenu (ID: ${downloadMenuItemId}) content checked:`, subMenuContent ? subMenuContent.innerHTML.substring(0, 500) + '...' : 'Not Found or Not Open');
                 try { document.body.click(); } catch(e){}
                 return false;
            }

            // Click the Format Item
             logDebug(`${logPrefix} Clicking format '${formatTextUpper}' option:`, formatItem);
             if (!simulateClick(formatItem)) {
                 log(`${logPrefix} Failed to simulate click on format '${formatTextUpper}' option. Aborting format ${format}.`, "error");
                 try { document.body.click(); } catch(e){}
                 return false;
             }

            await delay(DOWNLOAD_ACTION_DELAY);
            logDebug(`--- Finished processing ${logPrefix} (Assumed Success after Format Click) ---`);
            // Close menus *after* download action delay
             try { document.body.click(); await delay(50); } catch(e){}
            return true;
        }

        log(`${logPrefix} Reached unexpected end of function.`, "error");
        return false;
    }


    // --- Utility ---
    function setAllButtonsDisabled(disabled) {
        if (!uiElement || (isMinimized && disabled)) return;

        const buttons = uiElement.querySelectorAll('#riffControlContent button');
        buttons.forEach(btn => {
             if (btn.id !== 'minimizeButton') {
                 btn.disabled = disabled;
             }
         });

        const inputs = uiElement.querySelectorAll('#riffControlContent input, #riffControlContent select');
        inputs.forEach(input => {
             // Keep format and delay inputs always enabled for user interaction
             if (input.id.startsWith('format') || input.id.includes('DelayInput')) {
                  input.disabled = false;
             } else {
                  input.disabled = disabled;
             }
         });

        const labels = uiElement.querySelectorAll('#riffControlContent label');
         labels.forEach(label => {
             const isFormatLabel = label.closest('.downloadFormatContainer') !== null;
             const isDelayLabel = label.closest('.downloadDelayContainer') !== null;
             // Only disable non-format/non-delay labels when 'disabled' is true
             label.style.cursor = (disabled && !isFormatLabel && !isDelayLabel) ? 'not-allowed' : 'pointer';
             label.style.opacity = (disabled && !isFormatLabel && !isDelayLabel) ? '0.7' : '1';
         });

        // Handle song list checkboxes separately
        if(!disabled) {
            // Re-enable based on view logic when controls are enabled
            if(currentView === 'selective') populateDeleteSongListIfNeeded();
            if(currentView === 'download') populateDownloadSongListIfNeeded();
        } else {
            // Disable song list checkboxes when other controls are disabled
            uiElement.querySelectorAll('.songListContainer input[type="checkbox"]').forEach(cb => cb.disabled = true);
        }

        const status = document.getElementById('statusMessage');
        if (status) status.style.pointerEvents = disabled ? 'none' : 'auto';

        logDebug(`Controls ${disabled ? 'mostly disabled' : 'enabled'} (formats/delays always interactive)`);
    }


    // --- 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 {
                log(`Riffusion Multitool Script Loaded (v${GM_info.script.version}).`);
                createMainUI();
                log(`Initialized. Current View: ${currentView}. UI is ${isMinimized ? 'minimized (Top-Right)' : 'visible'}.`);
            } catch (e) {
                console.error("[RiffTool] Initialization failed:", e);
                alert("[RiffTool] Failed to initialize script. See console for errors.");
            }
        } else {
            logDebug("Not on the target /library/my-songs page.");
        }
    }

    waitForPageLoad(init);

})();