// INITIALIZATION
// ===============================
let navigationEpoch = 0;

function notifyPageWillChange(nextPageId) {
    const fromPageId = typeof currentPage === 'string' ? currentPage : null;
    if (fromPageId === nextPageId) return;

    window.dispatchEvent(
        new CustomEvent(PAGE_WILL_CHANGE_EVENT, {
            detail: {
                fromPageId,
                toPageId: nextPageId,
            },
        }),
    );
}

// ---- Accent Color System ----

function getAccentFallbackColors() {
    let accent = localStorage.getItem('soulsync-accent') || '#1db954';
    if (!/^#[0-9a-fA-F]{6}$/.test(accent)) accent = '#1db954';
    // Compute a lighter variant for the second color
    const r = parseInt(accent.slice(1, 3), 16), g = parseInt(accent.slice(3, 5), 16), b = parseInt(accent.slice(5, 7), 16);
    const lighter = '#' + [Math.min(r + 20, 255), Math.min(g + 30, 255), Math.min(b + 12, 255)]
        .map(v => v.toString(16).padStart(2, '0')).join('');
    return [accent, lighter];
}

function applyAccentColor(hex) {
    // Validate hex format — reject corrupt values
    if (typeof hex !== 'string' || !/^#[0-9a-fA-F]{6}$/.test(hex)) {
        hex = '#1db954'; // fallback to default
    }
    // Convert hex to RGB
    const r = parseInt(hex.slice(1, 3), 16);
    const g = parseInt(hex.slice(3, 5), 16);
    const b = parseInt(hex.slice(5, 7), 16);

    // Convert RGB to HSL
    const rn = r / 255, gn = g / 255, bn = b / 255;
    const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn);
    const l = (max + min) / 2;
    let h = 0, s = 0;
    if (max !== min) {
        const d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
        else if (max === gn) h = ((bn - rn) / d + 2) / 6;
        else h = ((rn - gn) / d + 4) / 6;
    }

    // Compute light variant: +16% lightness
    const lightL = Math.min(l + 0.16, 0.95);
    // Compute neon variant: high lightness + boosted saturation
    const neonL = Math.min(l + 0.30, 0.95);
    const neonS = Math.min(s + 0.1, 1.0);

    function hslToRgb(h, s, l) {
        if (s === 0) { const v = Math.round(l * 255); return [v, v, v]; }
        const hue2rgb = (p, q, t) => {
            if (t < 0) t += 1; if (t > 1) t -= 1;
            if (t < 1 / 6) return p + (q - p) * 6 * t;
            if (t < 1 / 2) return q;
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
            return p;
        };
        const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        const p = 2 * l - q;
        return [Math.round(hue2rgb(p, q, h + 1 / 3) * 255),
        Math.round(hue2rgb(p, q, h) * 255),
        Math.round(hue2rgb(p, q, h - 1 / 3) * 255)];
    }

    const light = hslToRgb(h, s, lightL);
    const neon = hslToRgb(h, neonS, neonL);

    const root = document.documentElement.style;
    root.setProperty('--accent-rgb', `${r}, ${g}, ${b}`);
    root.setProperty('--accent-light-rgb', `${light[0]}, ${light[1]}, ${light[2]}`);
    root.setProperty('--accent-neon-rgb', `${neon[0]}, ${neon[1]}, ${neon[2]}`);

    // Store for instant restore on next page load
    localStorage.setItem('soulsync-accent', hex);

    // Update preview swatch if it exists
    const swatch = document.getElementById('accent-preview-swatch');
    if (swatch) swatch.style.background = hex;
}

function applyParticlesSetting(enabled) {
    const canvas = document.getElementById('page-particles-canvas');
    if (canvas) canvas.style.display = enabled ? '' : 'none';
    if (window.pageParticles) {
        if (enabled) {
            const activePage = document.querySelector('.page.active');
            if (activePage) {
                window.pageParticles.setPage(activePage.id.replace('-page', ''));
            }
        } else {
            window.pageParticles.stop();
        }
    }
    window._particlesEnabled = enabled;
    localStorage.setItem('soulsync-particles', String(enabled));
}

function applyWorkerOrbsSetting(enabled) {
    window._workerOrbsEnabled = enabled;
    localStorage.setItem('soulsync-worker-orbs', String(enabled));
    if (window.workerOrbs) {
        if (enabled) {
            const activePage = document.querySelector('.page.active');
            if (activePage && activePage.id === 'dashboard-page') {
                window.workerOrbs.setPage('dashboard');
            }
        } else {
            window.workerOrbs.setPage('_disabled');
        }
    }
}

function initAccentColorListeners() {
    const presetSelect = document.getElementById('accent-preset');
    const customGroup = document.getElementById('custom-color-group');
    const customPicker = document.getElementById('accent-custom-color');
    if (!presetSelect) return;

    presetSelect.addEventListener('change', () => {
        const val = presetSelect.value;
        if (val === 'custom') {
            if (customGroup) customGroup.style.display = '';
            if (customPicker) applyAccentColor(customPicker.value);
        } else {
            if (customGroup) customGroup.style.display = 'none';
            applyAccentColor(val);
        }
    });

    if (customPicker) {
        customPicker.addEventListener('input', () => {
            applyAccentColor(customPicker.value);
        });
    }

    // Particles toggle — apply immediately on change
    const particlesCheckbox = document.getElementById('particles-enabled');
    if (particlesCheckbox) {
        particlesCheckbox.addEventListener('change', () => {
            applyParticlesSetting(particlesCheckbox.checked);
        });
    }

    // Worker orbs toggle — apply immediately on change
    const workerOrbsCheckbox = document.getElementById('worker-orbs-enabled');
    if (workerOrbsCheckbox) {
        workerOrbsCheckbox.addEventListener('change', () => {
            applyWorkerOrbsSetting(workerOrbsCheckbox.checked);
        });
    }

    // Reduce effects toggle — apply immediately on change
    const reduceEffectsCheckbox = document.getElementById('reduce-effects-enabled');
    if (reduceEffectsCheckbox) {
        reduceEffectsCheckbox.addEventListener('change', () => {
            applyReduceEffects(reduceEffectsCheckbox.checked);
        });
    }
}

function applyReduceEffects(enabled) {
    if (enabled) {
        document.body.classList.add('reduce-effects');
    } else {
        document.body.classList.remove('reduce-effects');
    }
    localStorage.setItem('soulsync-reduce-effects', enabled ? '1' : '0');
}

// Bootstrap accent and reduce-effects from localStorage instantly (prevents flash)
(function () {
    if (localStorage.getItem('soulsync-reduce-effects') === '1') {
        document.body.classList.add('reduce-effects');
    }
    const saved = localStorage.getItem('soulsync-accent');
    if (saved) applyAccentColor(saved);
    // Bootstrap particles setting from localStorage
    const particlesSaved = localStorage.getItem('soulsync-particles');
    if (particlesSaved === 'false') {
        window._particlesEnabled = false;
        const canvas = document.getElementById('page-particles-canvas');
        if (canvas) canvas.style.display = 'none';
    }
    // Bootstrap worker orbs setting from localStorage
    const workerOrbsSaved = localStorage.getItem('soulsync-worker-orbs');
    if (workerOrbsSaved === 'false') {
        window._workerOrbsEnabled = false;
    }
})();

// ── Profile System ─────────────────────────────────────────────
let currentProfile = null;
const PROFILE_CONTEXT_CHANGED_EVENT = 'ss:webui-profile-context-changed';

function notifyProfileContextChanged() {
    window.dispatchEvent(new CustomEvent(PROFILE_CONTEXT_CHANGED_EVENT));
}

function setCurrentProfile(profile) {
    currentProfile = profile;
    updateProfileIndicator();
    notifyProfileContextChanged();
}

// Temporary compatibility shim until existing profile rows are migrated to
// the current page ids.
const LEGACY_PROFILE_PAGE_ALIASES = {
    downloads: 'search',
    artists: 'search',
};

function normalizeProfilePageId(pageId) {
    return LEGACY_PROFILE_PAGE_ALIASES[pageId] || pageId;
}

function normalizeProfilePageList(pageIds) {
    if (!Array.isArray(pageIds)) return pageIds;
    return pageIds.map(normalizeProfilePageId);
}

function getProfileHomePage() {
    if (!currentProfile) return 'dashboard';
    if (currentProfile.home_page) return normalizeProfilePageId(currentProfile.home_page);
    return currentProfile.is_admin ? 'dashboard' : 'discover';
}

function isPageAllowed(pageId) {
    if (!currentProfile) return true;
    if (currentProfile.id === 1) return true;
    const normalizedPageId = normalizeProfilePageId(pageId);
    if (normalizedPageId === 'help' || normalizedPageId === 'issues') return true;
    if (normalizedPageId === 'settings') return currentProfile.is_admin;
    if (normalizedPageId === 'artist-detail') {
        const ap = normalizeProfilePageList(currentProfile.allowed_pages);
        if (!ap) return true;
        return ap.includes('library') || ap.includes('search');
    }
    const ap = normalizeProfilePageList(currentProfile.allowed_pages);
    if (!ap) return true; // null = all pages
    if (ap.includes(normalizedPageId)) return true;
    return false;
}

function canDownload() {
    if (!currentProfile) return true;
    if (currentProfile.id === 1) return true;
    return currentProfile.can_download !== false && currentProfile.can_download !== 0;
}

function getCurrentProfileContext() {
    if (!currentProfile) return null;
    return {
        profileId: currentProfile.id,
        isAdmin: !!currentProfile.is_admin,
    };
}

function activatePage(pageId, options = {}) {
    const forceReload = options.forceReload === true;
    const pageElement = document.getElementById(`${pageId}-page`);
    const isPageVisible = pageElement ? pageElement.classList.contains('active') : false;

    if (!forceReload && pageId === currentPage && isPageVisible) return;

    showLegacyPage(pageId);
    setActivePageChrome(pageId);
    loadPageData(pageId);
}

function renderProfileAvatar(el, profile) {
    // Renders avatar as image (if avatar_url set) or colored initial fallback
    // Preserves existing classes, ensures 'profile-avatar' is present
    if (!el.classList.contains('profile-avatar') && !el.classList.contains('profile-indicator-avatar') && !el.classList.contains('profile-pin-avatar')) {
        el.className = 'profile-avatar';
    }
    el.style.background = profile.avatar_color || '#6366f1';
    el.textContent = '';
    if (profile.avatar_url) {
        const img = document.createElement('img');
        img.src = profile.avatar_url;
        img.alt = profile.name;
        img.className = 'profile-avatar-img';
        img.onerror = () => {
            img.remove();
            el.textContent = profile.name.charAt(0).toUpperCase();
        };
        el.appendChild(img);
    } else {
        el.textContent = profile.name.charAt(0).toUpperCase();
    }
}

async function initProfileSystem() {
    try {
        // Check if a session already has a profile selected
        const currentRes = await fetch('/api/profiles/current');
        const currentData = await currentRes.json();
        if (currentData.success && currentData.profile) {
            setCurrentProfile(currentData.profile);

            // Check if launch PIN is required
            if (currentData.launch_pin_required) {
                showLaunchPinScreen();
                return false; // Defer app init until PIN verified
            }

            return true; // Profile already selected, skip picker
        }

        // Fetch all profiles
        const res = await fetch('/api/profiles');
        const data = await res.json();
        const profiles = data.profiles || [];

        if (profiles.length === 0) {
            // No profiles yet — auto-select admin profile 1
            await selectProfile(1);
            return true;
        }

        if (profiles.length === 1) {
            // Only one profile — always auto-select (PIN only matters with multiple profiles)
            await selectProfile(profiles[0].id);

            // Re-check for launch PIN after auto-select
            const recheck = await fetch('/api/profiles/current');
            const recheckData = await recheck.json();
            if (recheckData.launch_pin_required) {
                showLaunchPinScreen();
                return false;
            }

            return true;
        }

        // Multiple profiles or PIN required — show picker
        showProfilePicker(profiles);
        return false; // App init deferred until profile selected
    } catch (e) {
        console.error('Profile init error:', e);
        return true; // Fall through to normal init
    }
}

// ── Launch PIN Lock Screen ─────────────────────────────────────────────

function showLaunchPinScreen() {
    const overlay = document.getElementById('launch-pin-overlay');
    if (!overlay) return;
    overlay.style.display = 'flex';

    const input = document.getElementById('launch-pin-input');
    const submit = document.getElementById('launch-pin-submit');
    const error = document.getElementById('launch-pin-error');

    input.value = '';
    error.style.display = 'none';
    setTimeout(() => input.focus(), 100);

    const doSubmit = async () => {
        const pin = input.value.trim();
        if (!pin) return;

        submit.disabled = true;
        submit.textContent = 'Verifying...';

        try {
            const res = await fetch('/api/profiles/verify-launch-pin', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ pin })
            });
            const data = await res.json();

            if (data.success) {
                // Server session flag set by verify endpoint — consumed on next /api/profiles/current call
                overlay.style.display = 'none';
                initApp(); // Now safe to load the full app
            } else {
                error.textContent = data.error || 'Invalid PIN';
                error.style.display = 'block';
                input.value = '';
                input.focus();
                // Shake animation
                overlay.querySelector('.launch-pin-container').classList.add('shake');
                setTimeout(() => overlay.querySelector('.launch-pin-container').classList.remove('shake'), 500);
            }
        } catch (e) {
            error.textContent = 'Connection error';
            error.style.display = 'block';
        }

        submit.disabled = false;
        submit.textContent = 'Unlock';
    };

    // Remove old listeners to prevent stacking
    const newSubmit = submit.cloneNode(true);
    submit.parentNode.replaceChild(newSubmit, submit);
    newSubmit.addEventListener('click', doSubmit);

    input.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') doSubmit();
    });
}

// ── Security Settings Helpers ──────────────────────────────────────────

async function saveSecurityPin() {
    const pin = document.getElementById('security-new-pin').value;
    const confirm = document.getElementById('security-confirm-pin').value;
    const msg = document.getElementById('security-pin-msg');

    if (!pin || pin.length < 4) {
        msg.textContent = 'PIN must be at least 4 characters';
        msg.style.display = 'block';
        msg.style.color = '#ff5252';
        return;
    }
    if (pin !== confirm) {
        msg.textContent = 'PINs do not match';
        msg.style.display = 'block';
        msg.style.color = '#ff5252';
        return;
    }

    try {
        const res = await fetch('/api/profiles/1/set-pin', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ pin })
        });
        const data = await res.json();

        if (data.success) {
            msg.textContent = 'PIN saved! You can now enable the lock screen.';
            msg.style.color = '#4caf50';
            msg.style.display = 'block';

            // Update UI — hide setup, show change, enable toggle
            document.getElementById('security-pin-setup').style.display = 'none';
            document.getElementById('security-change-pin-section').style.display = 'block';
            document.getElementById('security-require-pin').disabled = false;

            // Clear inputs
            document.getElementById('security-new-pin').value = '';
            document.getElementById('security-confirm-pin').value = '';
        } else {
            msg.textContent = data.error || 'Failed to save PIN';
            msg.style.color = '#ff5252';
            msg.style.display = 'block';
        }
    } catch (e) {
        msg.textContent = 'Connection error';
        msg.style.color = '#ff5252';
        msg.style.display = 'block';
    }
}

function handleSecurityPinToggle(checkbox) {
    // If trying to enable but no PIN, show the setup section
    if (checkbox.checked) {
        const setupSection = document.getElementById('security-pin-setup');
        if (setupSection.style.display !== 'none' || checkbox.disabled) {
            checkbox.checked = false;
            setupSection.style.display = 'block';
            document.getElementById('security-new-pin').focus();
            return;
        }
    }
    // Auto-save this setting
    saveSettings(true);
}

function showChangeSecurityPin() {
    document.getElementById('security-pin-setup').style.display = 'block';
    document.getElementById('security-new-pin').focus();
}

// ── Forgot PIN Recovery ────────────────────────────────────────────────

function showForgotPinView() {
    document.getElementById('launch-pin-entry').style.display = 'none';
    document.getElementById('launch-pin-recovery').style.display = 'block';
    document.getElementById('launch-recovery-input').value = '';
    document.getElementById('launch-recovery-error').style.display = 'none';
    setTimeout(() => document.getElementById('launch-recovery-input').focus(), 100);
}

function showPinEntryView() {
    document.getElementById('launch-pin-recovery').style.display = 'none';
    document.getElementById('launch-pin-entry').style.display = 'block';
    setTimeout(() => document.getElementById('launch-pin-input').focus(), 100);
}

async function submitRecoveryCredential() {
    const input = document.getElementById('launch-recovery-input');
    const error = document.getElementById('launch-recovery-error');
    const btn = document.getElementById('launch-recovery-submit');
    const credential = input.value.trim();

    if (!credential) return;

    btn.disabled = true;
    btn.textContent = 'Verifying...';
    error.style.display = 'none';

    try {
        const res = await fetch('/api/profiles/reset-pin-via-credential', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ credential })
        });
        const data = await res.json();

        if (data.success) {
            sessionStorage.setItem('soulsync_pin_ok', '1');
            document.getElementById('launch-pin-overlay').style.display = 'none';
            initApp();
            setTimeout(() => showToast('PIN cleared. You can set a new one in Settings → Advanced.', 'success'), 1000);
        } else {
            error.textContent = data.error || 'Credential not recognized';
            error.style.display = 'block';
            input.value = '';
            input.focus();
            document.getElementById('launch-pin-container').classList.add('shake');
            setTimeout(() => document.getElementById('launch-pin-container').classList.remove('shake'), 500);
        }
    } catch (e) {
        error.textContent = 'Connection error';
        error.style.display = 'block';
    }

    btn.disabled = false;
    btn.textContent = 'Verify & Reset PIN';
}

// ── Profile PIN Forgot Recovery ────────────────────────────────────────
function showProfileForgotPin() {
    const dialog = document.getElementById('profile-pin-dialog');
    const content = dialog.querySelector('.profile-pin-content');

    // Store the profile ID we're recovering for
    const profileName = document.getElementById('profile-pin-name').textContent;

    // Replace dialog content with recovery form
    content.dataset.prevHtml = content.innerHTML;
    content.innerHTML = `
        <p style="color:#fff;font-size:14px;font-weight:600;margin-bottom:4px">Reset PIN for ${profileName}</p>
        <p style="color:rgba(255,255,255,0.5);font-size:12px;margin-bottom:12px">Enter any configured API credential<br>(Spotify secret, Plex token, etc.)</p>
        <input type="password" id="profile-recovery-input" class="profile-pin-input" maxlength="200" placeholder="Paste API credential" autocomplete="off">
        <div class="profile-pin-buttons">
            <button id="profile-recovery-cancel" class="profile-pin-cancel">Back</button>
            <button id="profile-recovery-submit" class="profile-pin-submit">Verify & Reset</button>
        </div>
        <p id="profile-recovery-error" class="profile-pin-error" style="display:none"></p>
    `;
    setTimeout(() => document.getElementById('profile-recovery-input').focus(), 100);

    document.getElementById('profile-recovery-cancel').onclick = () => {
        content.innerHTML = content.dataset.prevHtml;
    };

    document.getElementById('profile-recovery-submit').onclick = async () => {
        const input = document.getElementById('profile-recovery-input');
        const error = document.getElementById('profile-recovery-error');
        const credential = input.value.trim();
        if (!credential) return;

        const btn = document.getElementById('profile-recovery-submit');
        btn.disabled = true;
        btn.textContent = 'Verifying...';
        error.style.display = 'none';

        try {
            const res = await fetch('/api/profiles/reset-pin-via-credential', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ credential, profile_id: dialog._profileId || 1 })
            });
            const data = await res.json();
            if (data.success) {
                dialog.style.display = 'none';
                content.innerHTML = content.dataset.prevHtml;
                showToast('PIN cleared. You can set a new one in Settings.', 'success');
                // Re-try selecting the profile (now PIN-free)
                if (dialog._profileId) selectProfile(dialog._profileId);
            } else {
                error.textContent = data.error || 'Credential not recognized';
                error.style.display = 'block';
                input.value = '';
                input.focus();
            }
        } catch (e) {
            error.textContent = 'Connection error';
            error.style.display = 'block';
        }
        btn.disabled = false;
        btn.textContent = 'Verify & Reset';
    };

    document.getElementById('profile-recovery-input').onkeydown = (e) => {
        if (e.key === 'Enter') document.getElementById('profile-recovery-submit').click();
    };
}

function showProfilePicker(profiles, canCancel = false) {
    const overlay = document.getElementById('profile-picker-overlay');
    const grid = document.getElementById('profile-picker-grid');
    const actions = document.getElementById('profile-picker-actions');

    grid.innerHTML = '';
    profiles.forEach(p => {
        const card = document.createElement('div');
        card.className = 'profile-picker-card';
        const avatarEl = document.createElement('div');
        renderProfileAvatar(avatarEl, p);
        card.appendChild(avatarEl);
        const nameEl = document.createElement('span');
        nameEl.className = 'profile-name';
        nameEl.textContent = p.name;
        card.appendChild(nameEl);
        if (p.is_admin) {
            const badge = document.createElement('span');
            badge.className = 'profile-badge';
            badge.textContent = 'Admin';
            card.appendChild(badge);
        }
        card.onclick = () => handleProfileClick(p);
        grid.appendChild(card);
    });

    // Show actions: admin sees "Manage Profiles", non-admin sees "My Profile" (when they have a profile selected)
    const isAdmin = currentProfile ? currentProfile.is_admin : false;
    const manageBtn = document.getElementById('manage-profiles-btn');
    if (isAdmin) {
        actions.style.display = '';
        if (manageBtn) {
            manageBtn.textContent = 'Manage Profiles';
            // Reset onclick to admin handler (initProfileManagement sets this, but re-affirm here)
            manageBtn.onclick = () => {
                document.getElementById('profile-manage-panel').style.display = 'flex';
                loadProfileManageList();
            };
        }
    } else if (currentProfile && canCancel) {
        // Non-admin with an active profile: show "My Profile" to edit own settings
        actions.style.display = '';
        if (manageBtn) {
            manageBtn.textContent = 'My Profile';
            manageBtn.onclick = () => showSelfEditForm();
        }
    } else {
        actions.style.display = 'none';
    }

    // Show/remove cancel button when opened from sidebar indicator
    let cancelBtn = overlay.querySelector('.profile-picker-cancel');
    if (cancelBtn) cancelBtn.remove();
    if (canCancel) {
        cancelBtn = document.createElement('button');
        cancelBtn.className = 'profile-picker-cancel';
        cancelBtn.textContent = 'Cancel';
        cancelBtn.onclick = () => hideProfilePicker();
        actions.parentElement.appendChild(cancelBtn);
    }

    overlay.style.display = 'flex';
    document.querySelector('.main-container').style.display = 'none';
}

async function handleProfileClick(profile) {
    // Fetch profile count — PIN only matters with multiple profiles
    let profileCount = 1;
    try {
        const r = await fetch('/api/profiles');
        const d = await r.json();
        profileCount = (d.profiles || []).length;
    } catch (e) { }

    if (profile.has_pin && profileCount > 1) {
        showPinDialog(profile);
    } else {
        const wasSwitching = !!currentProfile;
        await selectProfile(profile.id);
        if (wasSwitching) {
            window.location.reload();
            return;
        }
        hideProfilePicker();
        initApp();
    }
}

function showPinDialog(profile) {
    const dialog = document.getElementById('profile-pin-dialog');
    const avatar = document.getElementById('profile-pin-avatar');
    const nameEl = document.getElementById('profile-pin-name');
    const errorEl = document.getElementById('profile-pin-error');
    const oldInput = document.getElementById('profile-pin-input');
    const oldSubmit = document.getElementById('profile-pin-submit');
    const oldCancel = document.getElementById('profile-pin-cancel');

    // Replace controls on every open so stale listeners from a previous
    // profile cannot submit the new PIN against the old profile id.
    const input = oldInput.cloneNode(true);
    const submit = oldSubmit.cloneNode(true);
    const cancel = oldCancel.cloneNode(true);
    oldInput.parentNode.replaceChild(input, oldInput);
    oldSubmit.parentNode.replaceChild(submit, oldSubmit);
    oldCancel.parentNode.replaceChild(cancel, oldCancel);

    renderProfileAvatar(avatar, profile);
    nameEl.textContent = profile.name;
    input.value = '';
    errorEl.style.display = 'none';
    dialog._profileId = profile.id;
    dialog.style.display = 'flex';
    setTimeout(() => input.focus(), 100);

    const wasSwitching = !!currentProfile;
    const handleSubmit = async () => {
        const pin = input.value;
        if (!pin) return;
        submit.disabled = true;
        submit.textContent = 'Verifying...';
        try {
            const res = await fetch('/api/profiles/select', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ profile_id: profile.id, pin })
            });
            const data = await res.json();
            if (data.success) {
                cleanup();
                if (wasSwitching) {
                    window.location.reload();
                    return;
                }
                dialog.style.display = 'none';
                hideProfilePicker();
                setCurrentProfile(data.profile);
                initApp();
                return;
            } else {
                errorEl.textContent = data.error || 'Invalid PIN';
                errorEl.style.display = '';
                input.value = '';
                input.focus();
            }
        } catch (e) {
            errorEl.textContent = 'Connection error';
            errorEl.style.display = '';
        }
        submit.disabled = false;
        submit.textContent = 'Submit';
    };

    const handleCancel = () => {
        dialog.style.display = 'none';
        cleanup();
    };

    const handleKeydown = (e) => {
        if (e.key === 'Enter') handleSubmit();
        if (e.key === 'Escape') handleCancel();
    };

    const cleanup = () => {
        submit.removeEventListener('click', handleSubmit);
        cancel.removeEventListener('click', handleCancel);
        input.removeEventListener('keydown', handleKeydown);
    };

    submit.addEventListener('click', handleSubmit);
    cancel.addEventListener('click', handleCancel);
    input.addEventListener('keydown', handleKeydown);
}

async function selectProfile(profileId) {
    try {
        const oldProfileId = currentProfile ? currentProfile.id : null;
        const res = await fetch('/api/profiles/select', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ profile_id: profileId })
        });
        const data = await res.json();
        if (data.success) {
            setCurrentProfile(data.profile);
            // Join profile-scoped WebSocket room for watchlist/wishlist count updates
            if (socket && socket.connected) {
                socket.emit('profile:join', { profile_id: profileId, old_profile_id: oldProfileId });
            }
            // Invalidate ListenBrainz cache on profile switch (each profile has their own playlists)
            _invalidateListenBrainzCache();
        }
        return data.success;
    } catch (e) {
        console.error('Error selecting profile:', e);
        return false;
    }
}

function hideProfilePicker() {
    document.getElementById('profile-picker-overlay').style.display = 'none';
    document.querySelector('.main-container').style.display = 'flex';
}

function updateProfileIndicator() {
    const indicator = document.getElementById('profile-indicator');
    if (!currentProfile || !indicator) return;

    const avatar = document.getElementById('profile-indicator-avatar');
    const name = document.getElementById('profile-indicator-name');

    renderProfileAvatar(avatar, currentProfile);
    name.textContent = currentProfile.name;
    indicator.style.display = 'flex';

    indicator.onclick = async () => {
        const res = await fetch('/api/profiles');
        const data = await res.json();
        if (data.profiles && data.profiles.length > 0) {
            showProfilePicker(data.profiles, true);
        }
    };

    // Filter sidebar pages based on profile permissions
    document.querySelectorAll('.nav-button[data-page]').forEach(btn => {
        const page = btn.getAttribute('data-page');
        if (page === 'hydrabase') return; // Managed by dev mode toggle
        if (page === 'settings') {
            // Settings always gated by is_admin
            btn.style.display = currentProfile.is_admin ? '' : 'none';
        } else if (page === 'help' || page === 'issues') {
            btn.style.display = ''; // Always visible
        } else if (currentProfile.id === 1) {
            btn.style.display = ''; // Root admin sees all
        } else {
            const ap = currentProfile.allowed_pages;
            btn.style.display = (!ap || ap.includes(page)) ? '' : 'none';
        }
    });

    // Toggle download capability
    if (canDownload()) {
        document.body.classList.remove('downloads-disabled');
    } else {
        document.body.classList.add('downloads-disabled');
    }
}

// =====================
// PERSONAL SETTINGS MODAL
// =====================

async function openPersonalSettings() {
    const overlay = document.getElementById('personal-settings-overlay');
    if (!overlay) return;
    overlay.style.display = 'flex';

    const body = document.getElementById('personal-settings-body');
    body.innerHTML = '<div style="text-align:center;padding:20px;color:rgba(255,255,255,0.4);">Loading...</div>';

    try {
        // Load all per-profile service data in parallel
        const [lbRes, spotifyRes] = await Promise.all([
            fetch('/api/profiles/me/listenbrainz'),
            fetch('/api/profiles/me/spotify'),
        ]);
        const lbData = await lbRes.json();
        const spotifyData = await spotifyRes.json();

        body.innerHTML = '';
        const isNonAdmin = currentProfile && !currentProfile.is_admin;

        if (isNonAdmin) {
            // Tabbed layout for non-admin with multiple sections
            const tabs = [
                { id: 'music', label: 'Music Services' },
                { id: 'server', label: 'Server' },
                { id: 'scrobble', label: 'Scrobbling' },
            ];
            const tabBar = document.createElement('div');
            tabBar.className = 'ps-tabbar';
            tabs.forEach((t, i) => {
                const btn = document.createElement('button');
                btn.className = 'ps-tab' + (i === 0 ? ' active' : '');
                btn.textContent = t.label;
                btn.onclick = () => {
                    tabBar.querySelectorAll('.ps-tab').forEach(b => b.classList.remove('active'));
                    btn.classList.add('active');
                    body.querySelectorAll('.ps-tab-content').forEach(c => c.classList.remove('active'));
                    const target = document.getElementById(`ps-tab-${t.id}`);
                    if (target) target.classList.add('active');
                };
                tabBar.appendChild(btn);
            });
            body.appendChild(tabBar);

            // Music Services tab
            const musicTab = document.createElement('div');
            musicTab.id = 'ps-tab-music';
            musicTab.className = 'ps-tab-content active';
            renderPersonalSettingsSpotify(musicTab, spotifyData);
            renderPersonalSettingsTidal(musicTab);
            body.appendChild(musicTab);

            // Server tab
            const serverTab = document.createElement('div');
            serverTab.id = 'ps-tab-server';
            serverTab.className = 'ps-tab-content';
            serverTab.innerHTML = '<div style="text-align:center;padding:20px;color:rgba(255,255,255,0.3);">Loading libraries...</div>';
            body.appendChild(serverTab);
            // Load server libraries async (don't block modal)
            fetch('/api/profiles/me/server-library').then(r => r.json()).then(libData => {
                serverTab.innerHTML = '';
                renderPersonalSettingsServerLibrary(serverTab, libData);
            }).catch(() => {
                serverTab.innerHTML = '';
                renderPersonalSettingsServerLibrary(serverTab, {});
            });

            // Scrobbling tab
            const scrobbleTab = document.createElement('div');
            scrobbleTab.id = 'ps-tab-scrobble';
            scrobbleTab.className = 'ps-tab-content';
            body.appendChild(scrobbleTab);
            // Render LB into the scrobble tab
            const origBody = body;
            renderPersonalSettingsLB(lbData, scrobbleTab);
        } else {
            // Admin: just ListenBrainz, no tabs
            const content = document.createElement('div');
            content.style.padding = '18px 22px 22px';
            body.appendChild(content);
            renderPersonalSettingsLB(lbData, content);
        }
    } catch (e) {
        body.innerHTML = '<div style="color:#ef4444;padding:16px;">Failed to load settings</div>';
    }
}

function closePersonalSettings() {
    const overlay = document.getElementById('personal-settings-overlay');
    if (overlay) overlay.style.display = 'none';
}

function renderPersonalSettingsSpotify(body, data) {
    const hasCreds = data.has_credentials;
    const clientId = data.client_id || '';

    let contentHtml;
    if (hasCreds) {
        contentHtml = `
            <div class="ps-connected-info">
                <div class="ps-connected-icon">🟢</div>
                <div class="ps-connected-details">
                    <div class="ps-connected-username">Credentials configured</div>
                    <div class="ps-connected-server">Client ID: ${escapeHtml(clientId.substring(0, 8))}...</div>
                    <div class="ps-connected-source">Personal Spotify app</div>
                </div>
            </div>
            <div class="ps-actions">
                <button class="ps-btn ps-btn-primary" onclick="authenticatePersonalSpotify()">🔐 Authenticate</button>
                <button class="ps-btn ps-btn-danger" onclick="disconnectPersonalSpotify()">Remove</button>
            </div>
        `;
    } else {
        contentHtml = `
            <div class="ps-form-group">
                <label>Client ID</label>
                <input type="text" id="ps-spotify-client-id" placeholder="Your Spotify Client ID">
            </div>
            <div class="ps-form-group">
                <label>Client Secret</label>
                <input type="password" id="ps-spotify-client-secret" placeholder="Your Spotify Client Secret">
            </div>
            <div class="ps-form-group">
                <label>Redirect URI <span style="font-weight:400;color:rgba(255,255,255,0.3)">(optional)</span></label>
                <input type="text" id="ps-spotify-redirect-uri" placeholder="http://127.0.0.1:8888/callback">
                <div class="ps-help-text">
                    Create an app at <a href="https://developer.spotify.com/dashboard" target="_blank">developer.spotify.com</a> and add the redirect URI
                </div>
            </div>
            <div id="ps-spotify-result"></div>
            <div class="ps-actions">
                <button class="ps-btn ps-btn-primary" onclick="savePersonalSpotify()">Save Credentials</button>
            </div>
        `;
    }

    const section = document.createElement('div');
    section.id = 'ps-spotify-section';
    section.innerHTML = `
        <div class="ps-section">
            <div class="ps-section-header">
                <h4 class="ps-section-title">Spotify</h4>
                <span class="ps-connection-badge ${hasCreds ? 'connected' : 'disconnected'}">
                    <span class="ps-connection-dot"></span>
                    ${hasCreds ? 'Configured' : 'Not configured'}
                </span>
            </div>
            <div class="ps-help-text" style="margin-bottom:12px;">
                Connect your own Spotify account to see your playlists instead of the admin's.
            </div>
            ${contentHtml}
        </div>
    `;

    const existing = document.getElementById('ps-spotify-section');
    if (existing) existing.replaceWith(section);
    else body.appendChild(section);
}

async function savePersonalSpotify() {
    const clientId = document.getElementById('ps-spotify-client-id')?.value?.trim();
    const clientSecret = document.getElementById('ps-spotify-client-secret')?.value?.trim();
    const redirectUri = document.getElementById('ps-spotify-redirect-uri')?.value?.trim();
    const resultEl = document.getElementById('ps-spotify-result');

    if (!clientId || !clientSecret) {
        if (resultEl) resultEl.innerHTML = '<div style="color:#ef4444;font-size:12px;margin-top:8px;">Client ID and Secret are required</div>';
        return;
    }

    try {
        const res = await fetch('/api/profiles/me/spotify', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri })
        });
        const data = await res.json();
        if (data.success) {
            showToast('Spotify credentials saved', 'success');
            openPersonalSettings(); // Reload to show connected state
        } else {
            if (resultEl) resultEl.innerHTML = `<div style="color:#ef4444;font-size:12px;margin-top:8px;">${data.error || 'Failed to save'}</div>`;
        }
    } catch (e) {
        if (resultEl) resultEl.innerHTML = '<div style="color:#ef4444;font-size:12px;margin-top:8px;">Network error</div>';
    }
}

async function authenticatePersonalSpotify() {
    // Trigger OAuth flow with profile_id in state so callback knows which profile
    window.open('/auth/spotify?profile_id=' + (currentProfile?.id || ''), '_blank');
}

function renderPersonalSettingsTidal(body) {
    const section = document.createElement('div');
    section.id = 'ps-tidal-section';
    section.innerHTML = `
        <div class="ps-section">
            <div class="ps-section-header">
                <h4 class="ps-section-title">Tidal</h4>
            </div>
            <div class="ps-help-text" style="margin-bottom:12px;">
                Connect your own Tidal account to see your playlists. Uses the admin's Tidal app credentials.
            </div>
            <div class="ps-actions">
                <button class="ps-btn ps-btn-primary" onclick="authenticatePersonalTidal()">🔐 Authenticate Tidal</button>
            </div>
        </div>
    `;
    const existing = document.getElementById('ps-tidal-section');
    if (existing) existing.replaceWith(section);
    else body.appendChild(section);
}

function authenticatePersonalTidal() {
    window.open('/auth/tidal?profile_id=' + (currentProfile?.id || ''), '_blank');
}

async function renderPersonalSettingsServerLibrary(container, profileData) {
    const section = document.createElement('div');
    section.id = 'ps-server-library-section';

    // Detect which server is active
    let serverType = 'none';
    let libraries = [];
    let users = [];
    const currentLib = profileData || {};

    try {
        // Try each server type to find the active one
        const plexRes = await fetch('/api/plex/music-libraries');
        if (plexRes.ok) {
            const plexData = await plexRes.json();
            if (plexData.libraries && plexData.libraries.length > 0) {
                serverType = 'plex';
                libraries = plexData.libraries;
            }
        }
    } catch (e) { }

    if (serverType === 'none') {
        try {
            const jellyRes = await fetch('/api/jellyfin/music-libraries');
            if (jellyRes.ok) {
                const jellyData = await jellyRes.json();
                if (jellyData.libraries && jellyData.libraries.length > 0) {
                    serverType = 'jellyfin';
                    libraries = jellyData.libraries;
                    users = jellyData.users || [];
                }
            }
        } catch (e) { }
    }

    if (serverType === 'none') {
        section.innerHTML = `
            <div class="ps-section">
                <div class="ps-section-header">
                    <h4 class="ps-section-title">Media Server</h4>
                </div>
                <div class="ps-help-text">No media server connected. Ask your admin to configure Plex, Jellyfin, or Navidrome in Settings.</div>
            </div>
        `;
    } else if (serverType === 'plex') {
        const selectedLib = currentLib.plex_library_id || '';
        const optionsHtml = libraries.map(lib => {
            const name = lib.name || lib.title || lib;
            const val = typeof lib === 'string' ? lib : (lib.name || lib.title);
            return `<option value="${escapeHtml(val)}" ${val === selectedLib ? 'selected' : ''}>${escapeHtml(val)}</option>`;
        }).join('');

        section.innerHTML = `
            <div class="ps-section">
                <div class="ps-section-header">
                    <h4 class="ps-section-title">Plex Library</h4>
                    <span class="ps-connection-badge ${selectedLib ? 'connected' : 'disconnected'}">
                        <span class="ps-connection-dot"></span>
                        ${selectedLib ? 'Custom' : 'Default'}
                    </span>
                </div>
                <div class="ps-help-text" style="margin-bottom:12px;">Choose which Plex music library your playlists sync to.</div>
                <div class="ps-form-group">
                    <label>Music Library</label>
                    <select id="ps-plex-library-select">
                        <option value="">Use admin default</option>
                        ${optionsHtml}
                    </select>
                </div>
                <div class="ps-actions">
                    <button class="ps-btn ps-btn-primary" onclick="savePersonalServerLibrary()">Save</button>
                </div>
            </div>
        `;
    } else if (serverType === 'jellyfin') {
        const selectedUser = currentLib.jellyfin_user_id || '';
        const selectedLib = currentLib.jellyfin_library_id || '';

        const userOpts = users.map(u => {
            const uid = u.id || u.Id;
            const uname = u.name || u.Name;
            return `<option value="${escapeHtml(uid)}" ${uid === selectedUser ? 'selected' : ''}>${escapeHtml(uname)}</option>`;
        }).join('');

        const libOpts = libraries.map(lib => {
            const lid = lib.key || lib.id || lib.Id;
            const lname = lib.name || lib.Name || lib.title;
            return `<option value="${escapeHtml(lid)}" ${lid === selectedLib ? 'selected' : ''}>${escapeHtml(lname)}</option>`;
        }).join('');

        section.innerHTML = `
            <div class="ps-section">
                <div class="ps-section-header">
                    <h4 class="ps-section-title">Jellyfin</h4>
                    <span class="ps-connection-badge ${selectedUser || selectedLib ? 'connected' : 'disconnected'}">
                        <span class="ps-connection-dot"></span>
                        ${selectedUser || selectedLib ? 'Custom' : 'Default'}
                    </span>
                </div>
                <div class="ps-help-text" style="margin-bottom:12px;">Choose which Jellyfin user and library your playlists sync to.</div>
                ${users.length ? `<div class="ps-form-group"><label>User</label><select id="ps-jellyfin-user-select"><option value="">Use admin default</option>${userOpts}</select></div>` : ''}
                <div class="ps-form-group">
                    <label>Music Library</label>
                    <select id="ps-jellyfin-library-select">
                        <option value="">Use admin default</option>
                        ${libOpts}
                    </select>
                </div>
                <div class="ps-actions">
                    <button class="ps-btn ps-btn-primary" onclick="savePersonalServerLibrary()">Save</button>
                </div>
            </div>
        `;
    }

    const existing = document.getElementById('ps-server-library-section');
    if (existing) existing.replaceWith(section);
    else container.appendChild(section);
}

async function savePersonalServerLibrary() {
    try {
        const plexSelect = document.getElementById('ps-plex-library-select');
        const jellyUserSelect = document.getElementById('ps-jellyfin-user-select');
        const jellyLibSelect = document.getElementById('ps-jellyfin-library-select');

        if (plexSelect) {
            await fetch('/api/profiles/me/server-library', {
                method: 'POST', headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ server_type: 'plex', library_id: plexSelect.value || null })
            });
        }
        if (jellyUserSelect || jellyLibSelect) {
            await fetch('/api/profiles/me/server-library', {
                method: 'POST', headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    server_type: 'jellyfin',
                    user_id: jellyUserSelect?.value || null,
                    library_id: jellyLibSelect?.value || null
                })
            });
        }

        showToast('Server library settings saved', 'success');
    } catch (e) {
        showToast('Error saving settings', 'error');
    }
}

async function disconnectPersonalSpotify() {
    try {
        const res = await fetch('/api/profiles/me/spotify', { method: 'DELETE' });
        const data = await res.json();
        if (data.success) {
            showToast('Spotify credentials removed — using shared config', 'info');
            openPersonalSettings(); // Reload
        }
    } catch (e) {
        showToast('Error removing credentials', 'error');
    }
}

function renderPersonalSettingsLB(data, container) {
    const body = container || document.getElementById('personal-settings-body');
    const connected = data.connected;
    const username = data.username || '';
    const baseUrl = data.base_url || '';
    const source = data.source || 'global';

    const tokenFormHtml = `
        <div class="ps-form-group">
            <label>User Token</label>
            <input type="password" id="ps-lb-token" placeholder="Paste your ListenBrainz token">
        </div>
        <div class="ps-form-group">
            <label>Server URL <span style="font-weight:400;color:rgba(255,255,255,0.3)">(optional)</span></label>
            <input type="text" id="ps-lb-base-url" placeholder="Leave empty for official (api.listenbrainz.org)">
            <div class="ps-help-text">
                Get your token from <a href="https://listenbrainz.org/profile/" target="_blank">listenbrainz.org/profile</a>
            </div>
        </div>
        <div id="ps-lb-result"></div>
        <div class="ps-actions">
            <button class="ps-btn ps-btn-secondary" onclick="testPersonalListenBrainz()">Test</button>
            <button class="ps-btn ps-btn-primary" onclick="connectPersonalListenBrainz()">Connect</button>
        </div>
    `;

    let contentHtml;
    if (connected && source === 'profile') {
        // Personal token — show connected state with Disconnect
        const serverDisplay = baseUrl ? baseUrl.replace(/\/1$/, '').replace(/^https?:\/\//, '') : 'api.listenbrainz.org';
        contentHtml = `
            <div class="ps-connected-info">
                <div class="ps-connected-icon">&#129504;</div>
                <div class="ps-connected-details">
                    <div class="ps-connected-username">Connected as ${escapeHtml(username)}</div>
                    <div class="ps-connected-server">${escapeHtml(serverDisplay)}</div>
                    <div class="ps-connected-source">Personal token</div>
                </div>
            </div>
            <div class="ps-actions">
                <button class="ps-btn ps-btn-danger" onclick="disconnectPersonalListenBrainz()">Disconnect</button>
            </div>
        `;
    } else if (connected && source === 'global') {
        // Using admin's shared token — show status + option to set own token
        const serverDisplay = baseUrl ? baseUrl.replace(/\/1$/, '').replace(/^https?:\/\//, '') : 'api.listenbrainz.org';
        contentHtml = `
            <div class="ps-connected-info">
                <div class="ps-connected-icon">&#129504;</div>
                <div class="ps-connected-details">
                    <div class="ps-connected-username">Connected as ${escapeHtml(username)}</div>
                    <div class="ps-connected-server">${escapeHtml(serverDisplay)}</div>
                    <div class="ps-connected-source">Using shared token from Settings</div>
                </div>
            </div>
            <div style="margin-top:14px;padding-top:14px;border-top:1px solid rgba(255,255,255,0.06);">
                <div style="font-size:11px;color:rgba(255,255,255,0.45);margin-bottom:10px;">Set your own token to use a different ListenBrainz account:</div>
                ${tokenFormHtml}
            </div>
        `;
    } else {
        // Not connected at all
        contentHtml = tokenFormHtml;
    }

    const section = document.createElement('div');
    section.id = 'ps-listenbrainz-section';
    section.innerHTML = `
        <div class="ps-section">
            <div class="ps-section-header">
                <h4 class="ps-section-title">ListenBrainz</h4>
                <span class="ps-connection-badge ${connected ? 'connected' : 'disconnected'}">
                    <span class="ps-connection-dot"></span>
                    ${connected ? 'Connected' : 'Not connected'}
                </span>
            </div>
            ${contentHtml}
        </div>
    `;
    // Replace existing or append
    const existing = document.getElementById('ps-listenbrainz-section');
    if (existing) existing.replaceWith(section);
    else body.appendChild(section);
}

async function testPersonalListenBrainz() {
    const token = document.getElementById('ps-lb-token')?.value?.trim();
    const baseUrl = document.getElementById('ps-lb-base-url')?.value?.trim() || '';
    const resultEl = document.getElementById('ps-lb-result');
    if (!token) {
        if (resultEl) resultEl.innerHTML = '<div class="ps-inline-result error">Please enter a token</div>';
        return;
    }
    if (resultEl) resultEl.innerHTML = '<div class="ps-inline-result" style="color:rgba(255,255,255,0.5);">Testing...</div>';
    try {
        const res = await fetch('/api/profiles/me/listenbrainz/test', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ token, base_url: baseUrl })
        });
        const data = await res.json();
        if (data.success) {
            resultEl.innerHTML = `<div class="ps-inline-result success">Valid token — ${escapeHtml(data.username)}</div>`;
        } else {
            resultEl.innerHTML = `<div class="ps-inline-result error">${escapeHtml(data.error || 'Invalid token')}</div>`;
        }
    } catch (e) {
        resultEl.innerHTML = '<div class="ps-inline-result error">Connection failed</div>';
    }
}

async function connectPersonalListenBrainz() {
    const token = document.getElementById('ps-lb-token')?.value?.trim();
    const baseUrl = document.getElementById('ps-lb-base-url')?.value?.trim() || '';
    const resultEl = document.getElementById('ps-lb-result');
    if (!token) {
        if (resultEl) resultEl.innerHTML = '<div class="ps-inline-result error">Please enter a token</div>';
        return;
    }
    // Disable buttons during connect
    document.querySelectorAll('.ps-actions .ps-btn').forEach(b => b.disabled = true);
    if (resultEl) resultEl.innerHTML = '<div class="ps-inline-result" style="color:rgba(255,255,255,0.5);">Connecting...</div>';
    try {
        const res = await fetch('/api/profiles/me/listenbrainz', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ token, base_url: baseUrl })
        });
        const data = await res.json();
        if (data.success) {
            showToast(`Connected to ListenBrainz as ${data.username}`, 'success');
            // Re-render as connected
            renderPersonalSettingsLB({ connected: true, username: data.username, base_url: baseUrl, source: 'profile' });
            // Refresh LB playlists on discover page
            _invalidateListenBrainzCache();
            if (typeof initializeListenBrainzTabs === 'function') {
                initializeListenBrainzTabs();
            }
        } else {
            resultEl.innerHTML = `<div class="ps-inline-result error">${escapeHtml(data.error || 'Connection failed')}</div>`;
            document.querySelectorAll('.ps-actions .ps-btn').forEach(b => b.disabled = false);
        }
    } catch (e) {
        resultEl.innerHTML = '<div class="ps-inline-result error">Connection failed</div>';
        document.querySelectorAll('.ps-actions .ps-btn').forEach(b => b.disabled = false);
    }
}

async function disconnectPersonalListenBrainz() {
    try {
        await fetch('/api/profiles/me/listenbrainz', { method: 'DELETE' });
        showToast('ListenBrainz disconnected', 'info');
        // Re-render as disconnected — re-fetch to check if global fallback exists
        const res = await fetch('/api/profiles/me/listenbrainz');
        const data = await res.json();
        renderPersonalSettingsLB(data);
        // Refresh LB playlists on discover page
        _invalidateListenBrainzCache();
        if (typeof initializeListenBrainzTabs === 'function') {
            initializeListenBrainzTabs();
        }
    } catch (e) {
        showToast('Failed to disconnect', 'error');
    }
}

function _invalidateListenBrainzCache() {
    if (typeof listenbrainzPlaylistsLoaded !== 'undefined') listenbrainzPlaylistsLoaded = false;
    if (typeof listenbrainzPlaylistsCache !== 'undefined') {
        try { Object.keys(listenbrainzPlaylistsCache).forEach(k => delete listenbrainzPlaylistsCache[k]); } catch (e) { }
    }
    if (typeof listenbrainzTracksCache !== 'undefined') {
        try { Object.keys(listenbrainzTracksCache).forEach(k => delete listenbrainzTracksCache[k]); } catch (e) { }
    }
}

const PROFILE_PAGE_LABELS = {
    dashboard: 'Dashboard',
    sync: 'Sync',
    search: 'Search',
    discover: 'Discover',
    watchlist: 'Watchlist',
    wishlist: 'Wishlist',
    automations: 'Automations',
    'active-downloads': 'Downloads',
    library: 'Library',
    stats: 'Listening Stats',
    'playlist-explorer': 'Playlist Explorer',
    import: 'Import',
    tools: 'Tools',
    hydrabase: 'Hydrabase',
    issues: 'Issues',
    help: 'Help & Docs',
    settings: 'Settings',
    'artist-detail': 'Artist Detail',
};

function getProfilePageLabel(pageId) {
    return PROFILE_PAGE_LABELS[pageId] || pageId.split('-').map(part => part ? part[0].toUpperCase() + part.slice(1) : part).join(' ');
}

function getProfilePageSelectOptions(profileSettings = {}) {
    const options = [];
    const seen = new Set();
    const homeSelect = document.getElementById('new-profile-home-page');
    const normalizedHomePage = normalizeProfilePageId(profileSettings.home_page);

    if (homeSelect) {
        homeSelect.querySelectorAll('option').forEach(option => {
            if (!option.value || seen.has(option.value)) return;
            options.push({
                value: option.value,
                label: option.textContent?.trim() || getProfilePageLabel(option.value),
            });
            seen.add(option.value);
        });
    }

    if (normalizedHomePage && !seen.has(normalizedHomePage)) {
        options.push({
            value: normalizedHomePage,
            label: getProfilePageLabel(normalizedHomePage),
        });
        seen.add(normalizedHomePage);
    }

    return options;
}

function getProfilePageAccessOptions(profileSettings = {}) {
    const options = [];
    const seen = new Set();
    const allowedSet = Array.isArray(profileSettings.allowed_pages)
        ? new Set(normalizeProfilePageList(profileSettings.allowed_pages))
        : null;
    const accessContainer = document.getElementById('new-profile-allowed-pages');

    if (accessContainer) {
        accessContainer.querySelectorAll('input[type="checkbox"]').forEach(cb => {
            if (seen.has(cb.value)) return;
            options.push({
                value: cb.value,
                label: cb.parentElement?.textContent?.trim() || getProfilePageLabel(cb.value),
                checked: cb.disabled ? true : (allowedSet ? allowedSet.has(cb.value) : true),
                disabled: cb.disabled,
            });
            seen.add(cb.value);
        });
    }

    if (allowedSet) {
        allowedSet.forEach(pageId => {
            if (seen.has(pageId)) return;
            options.push({
                value: pageId,
                label: getProfilePageLabel(pageId),
                checked: true,
                disabled: false,
            });
            seen.add(pageId);
        });
    }

    return options;
}

function initProfileManagement() {
    const manageBtn = document.getElementById('manage-profiles-btn');
    const closeBtn = document.getElementById('profile-manage-close');
    const createBtn = document.getElementById('create-profile-btn');
    const adminPinBtn = document.getElementById('set-admin-pin-btn');

    if (manageBtn) {
        manageBtn.onclick = () => {
            document.getElementById('profile-manage-panel').style.display = 'flex';
            loadProfileManageList();
        };
    }

    if (closeBtn) {
        closeBtn.onclick = () => {
            document.getElementById('profile-manage-panel').style.display = 'none';
            // Refresh picker — keep cancel button if user already has a profile selected
            const hasCancel = !!currentProfile;
            fetch('/api/profiles').then(r => r.json()).then(d => {
                showProfilePicker(d.profiles || [], hasCancel);
            });
        };
    }

    // Color picker
    let selectedColor = '#6366f1';
    document.querySelectorAll('.profile-color-swatch').forEach(swatch => {
        swatch.onclick = () => {
            document.querySelectorAll('.profile-color-swatch').forEach(s => s.classList.remove('selected'));
            swatch.classList.add('selected');
            selectedColor = swatch.dataset.color;
        };
    });
    // Select first by default
    const firstSwatch = document.querySelector('.profile-color-swatch');
    if (firstSwatch) firstSwatch.classList.add('selected');

    if (createBtn) {
        createBtn.onclick = async () => {
            const name = document.getElementById('new-profile-name').value.trim();
            const avatarUrl = document.getElementById('new-profile-avatar-url').value.trim();
            const pin = document.getElementById('new-profile-pin').value;
            if (!name) return;

            // Collect profile settings
            const homePage = document.getElementById('new-profile-home-page').value || null;
            const pageCheckboxes = document.querySelectorAll('#new-profile-allowed-pages input[type="checkbox"]:not(:disabled)');
            const allChecked = Array.from(pageCheckboxes).every(cb => cb.checked);
            const allowedPages = allChecked ? null : Array.from(pageCheckboxes).filter(cb => cb.checked).map(cb => cb.value);
            const canDl = document.getElementById('new-profile-can-download').checked;

            const res = await fetch('/api/profiles', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    name, avatar_color: selectedColor,
                    avatar_url: avatarUrl || undefined,
                    pin: pin || undefined,
                    home_page: homePage,
                    allowed_pages: allowedPages,
                    can_download: canDl
                })
            });
            const data = await res.json();
            if (data.success) {
                document.getElementById('new-profile-name').value = '';
                document.getElementById('new-profile-avatar-url').value = '';
                document.getElementById('new-profile-pin').value = '';
                document.getElementById('new-profile-home-page').value = '';
                pageCheckboxes.forEach(cb => cb.checked = true);
                document.getElementById('new-profile-can-download').checked = true;
                loadProfileManageList();
                // Show admin PIN section if >1 profiles and admin has no PIN
                checkAdminPinRequired();
            } else {
                alert(data.error || 'Failed to create profile');
            }
        };
    }

    if (adminPinBtn) {
        adminPinBtn.onclick = async () => {
            const pin = document.getElementById('admin-pin-input').value;
            if (!pin || pin.length < 1) return;
            // Find admin profile
            const res = await fetch('/api/profiles');
            const data = await res.json();
            const admin = (data.profiles || []).find(p => p.is_admin);
            if (!admin) return;

            try {
                const pinRes = await fetch(`/api/profiles/${admin.id}/set-pin`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ pin })
                });
                const pinData = await pinRes.json();
                if (!pinData.success) {
                    alert(pinData.error || 'Failed to set PIN');
                    return;
                }
            } catch (e) {
                alert('Connection error');
                return;
            }
            document.getElementById('admin-pin-input').value = '';
            document.getElementById('admin-pin-section').style.display = 'none';
            loadProfileManageList();
        };
    }
}

async function loadProfileManageList() {
    const list = document.getElementById('profile-manage-list');
    const res = await fetch('/api/profiles');
    const data = await res.json();
    const profiles = data.profiles || [];

    list.innerHTML = '';
    profiles.forEach(p => {
        const item = document.createElement('div');
        item.className = 'profile-manage-item';

        const av = document.createElement('div');
        renderProfileAvatar(av, p);
        item.appendChild(av);

        const info = document.createElement('div');
        info.className = 'profile-info';
        const nameDiv = document.createElement('div');
        nameDiv.className = 'name';
        nameDiv.textContent = p.name + (p.has_pin ? ' 🔒' : '');
        info.appendChild(nameDiv);
        const roleTags = [];
        if (p.is_admin) roleTags.push('Admin');
        if (p.can_download === false) roleTags.push('No Downloads');
        if (p.allowed_pages) roleTags.push(`${p.allowed_pages.length} pages`);
        if (roleTags.length) {
            const roleDiv = document.createElement('div');
            roleDiv.className = 'role';
            roleDiv.textContent = roleTags.join(' · ');
            info.appendChild(roleDiv);
        }
        item.appendChild(info);

        const actions = document.createElement('div');
        actions.className = 'profile-manage-actions';

        const editBtn = document.createElement('button');
        editBtn.className = 'profile-edit-btn';
        editBtn.dataset.id = p.id;
        editBtn.dataset.name = p.name;
        editBtn.dataset.color = p.avatar_color || '#6366f1';
        editBtn.dataset.avatarUrl = p.avatar_url || '';
        editBtn.dataset.homePage = p.home_page || '';
        editBtn.dataset.allowedPages = p.allowed_pages ? JSON.stringify(p.allowed_pages) : '';
        editBtn.dataset.canDownload = p.can_download !== false ? '1' : '0';
        editBtn.dataset.isAdmin = p.is_admin ? '1' : '0';
        editBtn.title = 'Edit profile';
        editBtn.textContent = '✏️';
        actions.appendChild(editBtn);

        if (!p.is_admin) {
            const delBtn = document.createElement('button');
            delBtn.className = 'profile-delete-btn';
            delBtn.dataset.id = p.id;
            delBtn.title = 'Delete profile';
            delBtn.textContent = '🗑️';
            actions.appendChild(delBtn);
        }

        item.appendChild(actions);
        list.appendChild(item);
    });

    // Bind edit buttons
    list.querySelectorAll('.profile-edit-btn').forEach(btn => {
        btn.onclick = () => {
            showProfileEditForm(btn.dataset.id, btn.dataset.name, btn.dataset.color, btn.dataset.avatarUrl, {
                home_page: btn.dataset.homePage || '',
                allowed_pages: btn.dataset.allowedPages ? JSON.parse(btn.dataset.allowedPages) : null,
                can_download: btn.dataset.canDownload !== '0',
                is_admin: btn.dataset.isAdmin === '1'
            });
        };
    });

    // Bind delete buttons
    list.querySelectorAll('.profile-delete-btn').forEach(btn => {
        btn.onclick = async () => {
            if (!await showConfirmDialog({ title: 'Delete Profile', message: 'Delete this profile and all its data?', confirmText: 'Delete', destructive: true })) return;
            try {
                const res = await fetch(`/api/profiles/${btn.dataset.id}`, { method: 'DELETE' });
                const data = await res.json();
                if (!data.success) {
                    alert(data.error || 'Failed to delete profile');
                }
            } catch (e) {
                alert('Connection error');
            }
            loadProfileManageList();
        };
    });

    checkAdminPinRequired();
}

function showProfileEditForm(profileId, currentName, currentColor, currentAvatarUrl, profileSettings = {}) {
    const list = document.getElementById('profile-manage-list');
    // Remove any existing edit form
    const existing = document.getElementById('profile-edit-form');
    if (existing) existing.remove();

    const isAdmin = currentProfile && currentProfile.is_admin;
    const isEditingAdmin = profileSettings.is_admin;
    const editColors = ['#6366f1', '#ec4899', '#10b981', '#f59e0b', '#3b82f6', '#ef4444', '#8b5cf6', '#14b8a6'];
    const pageSelectOptions = getProfilePageSelectOptions(profileSettings);
    const pageAccessOptions = getProfilePageAccessOptions(profileSettings);

    const form = document.createElement('div');
    form.id = 'profile-edit-form';
    form.className = 'profile-edit-form';

    const nameInput = document.createElement('input');
    nameInput.type = 'text';
    nameInput.className = 'profile-input';
    nameInput.value = currentName;
    nameInput.maxLength = 20;
    nameInput.placeholder = 'Profile name';
    form.appendChild(nameInput);

    const urlInput = document.createElement('input');
    urlInput.type = 'url';
    urlInput.className = 'profile-input';
    urlInput.value = currentAvatarUrl || '';
    urlInput.placeholder = 'Avatar image URL (optional)';
    form.appendChild(urlInput);

    const colorRow = document.createElement('div');
    colorRow.className = 'profile-color-picker';
    let editColor = currentColor;
    editColors.forEach(c => {
        const swatch = document.createElement('span');
        swatch.className = 'profile-color-swatch' + (c === currentColor ? ' selected' : '');
        swatch.style.background = c;
        swatch.dataset.color = c;
        swatch.onclick = () => {
            colorRow.querySelectorAll('.profile-color-swatch').forEach(s => s.classList.remove('selected'));
            swatch.classList.add('selected');
            editColor = c;
        };
        colorRow.appendChild(swatch);
    });
    form.appendChild(colorRow);

    // Home page selector — visible to everyone (self-edit or admin editing others)
    const homeLabel = document.createElement('label');
    homeLabel.className = 'profile-settings-label';
    homeLabel.textContent = 'Home Page';
    form.appendChild(homeLabel);

    const homeSelect = document.createElement('select');
    homeSelect.className = 'profile-input';
    const defaultOpt = document.createElement('option');
    defaultOpt.value = '';
    defaultOpt.textContent = isEditingAdmin ? 'Default (Dashboard)' : 'Default (Discover)';
    homeSelect.appendChild(defaultOpt);
    const normalizedHome = profileSettings.home_page;
    pageSelectOptions.forEach(({ value, label }) => {
        const opt = document.createElement('option');
        opt.value = value;
        opt.textContent = label;
        if (value === normalizedHome) opt.selected = true;
        homeSelect.appendChild(opt);
    });
    form.appendChild(homeSelect);

    // Admin-only settings: allowed pages & can_download
    let pageCheckboxes = [];
    let canDlCheckbox = null;
    if (isAdmin && !isEditingAdmin) {
        const apLabel = document.createElement('label');
        apLabel.className = 'profile-settings-label';
        apLabel.textContent = 'Page Access';
        form.appendChild(apLabel);

        const apContainer = document.createElement('div');
        apContainer.className = 'profile-page-checkboxes';
        pageAccessOptions.forEach(({ value, label, checked, disabled }) => {
            const lbl = document.createElement('label');
            const cb = document.createElement('input');
            cb.type = 'checkbox';
            cb.value = value;
            cb.checked = checked;
            cb.disabled = disabled;
            lbl.appendChild(cb);
            lbl.appendChild(document.createTextNode(' ' + label));
            apContainer.appendChild(lbl);
            pageCheckboxes.push(cb);
        });
        form.appendChild(apContainer);

        const dlLabel = document.createElement('label');
        dlLabel.className = 'profile-checkbox-label';
        canDlCheckbox = document.createElement('input');
        canDlCheckbox.type = 'checkbox';
        canDlCheckbox.checked = profileSettings.can_download !== false;
        dlLabel.appendChild(canDlCheckbox);
        dlLabel.appendChild(document.createTextNode(' Can download music'));
        form.appendChild(dlLabel);
    }

    const btnRow = document.createElement('div');
    btnRow.className = 'profile-edit-buttons';

    const saveBtn = document.createElement('button');
    saveBtn.className = 'profile-create-btn';
    saveBtn.textContent = 'Save';
    saveBtn.onclick = async () => {
        const newName = nameInput.value.trim();
        if (!newName) { alert('Name cannot be empty'); return; }
        const newAvatarUrl = urlInput.value.trim() || null;
        const payload = { name: newName, avatar_color: editColor, avatar_url: newAvatarUrl };

        // Home page
        payload.home_page = homeSelect.value || null;

        // Admin-only fields
        if (isAdmin && !isEditingAdmin && pageCheckboxes.length) {
            const editablePageCheckboxes = pageCheckboxes.filter(cb => !cb.disabled);
            const allChecked = editablePageCheckboxes.every(cb => cb.checked);
            payload.allowed_pages = allChecked ? null : editablePageCheckboxes.filter(cb => cb.checked).map(cb => cb.value);
            payload.can_download = canDlCheckbox ? canDlCheckbox.checked : true;
        }

        try {
            const res = await fetch(`/api/profiles/${profileId}`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(payload)
            });
            const data = await res.json();
            if (data.success) {
                // Update sidebar indicator if editing current profile
                if (currentProfile && currentProfile.id == profileId) {
                    currentProfile.name = newName;
                    currentProfile.avatar_color = editColor;
                    currentProfile.avatar_url = newAvatarUrl;
                    if (payload.home_page !== undefined) currentProfile.home_page = payload.home_page;
                    if (payload.allowed_pages !== undefined) currentProfile.allowed_pages = payload.allowed_pages;
                    if (payload.can_download !== undefined) currentProfile.can_download = payload.can_download;
                    updateProfileIndicator();
                    notifyProfileContextChanged();
                }
                loadProfileManageList();
            } else {
                alert(data.error || 'Failed to update profile');
            }
        } catch (e) {
            alert('Connection error');
        }
    };
    btnRow.appendChild(saveBtn);

    const cancelBtn = document.createElement('button');
    cancelBtn.className = 'profile-picker-cancel';
    cancelBtn.textContent = 'Cancel';
    cancelBtn.onclick = () => form.remove();
    btnRow.appendChild(cancelBtn);

    form.appendChild(btnRow);
    list.appendChild(form);
    nameInput.focus();
    nameInput.select();
}

function showSelfEditForm() {
    if (!currentProfile) return;
    const overlay = document.getElementById('profile-picker-overlay');
    const container = overlay.querySelector('.profile-picker-container');

    // Hide the picker grid and show self-edit form
    const grid = document.getElementById('profile-picker-grid');
    const actions = document.getElementById('profile-picker-actions');
    grid.style.display = 'none';
    actions.style.display = 'none';

    // Remove any existing self-edit form
    const existing = document.getElementById('self-edit-form');
    if (existing) existing.remove();

    const pageLabels = {
        dashboard: 'Dashboard', sync: 'Sync', search: 'Search', discover: 'Discover',
        automations: 'Automations', library: 'Library', stats: 'Listening Stats',
        'playlist-explorer': 'Playlist Explorer', import: 'Import', help: 'Help & Docs'
    };

    const form = document.createElement('div');
    form.id = 'self-edit-form';
    form.className = 'profile-edit-form';
    form.style.marginTop = '16px';

    const title = document.createElement('h3');
    title.textContent = 'My Profile';
    title.style.cssText = 'color: #fff; margin: 0 0 12px; font-size: 18px;';
    form.appendChild(title);

    // Name
    const nameInput = document.createElement('input');
    nameInput.type = 'text';
    nameInput.className = 'profile-input';
    nameInput.value = currentProfile.name;
    nameInput.maxLength = 20;
    nameInput.placeholder = 'Profile name';
    form.appendChild(nameInput);

    // Home page
    const homeLabel = document.createElement('label');
    homeLabel.className = 'profile-settings-label';
    homeLabel.textContent = 'Home Page';
    form.appendChild(homeLabel);

    const homeSelect = document.createElement('select');
    homeSelect.className = 'profile-input';
    const defaultOpt = document.createElement('option');
    defaultOpt.value = '';
    defaultOpt.textContent = 'Default (Discover)';
    homeSelect.appendChild(defaultOpt);
    const normalizedHome = currentProfile.home_page;
    getProfilePageSelectOptions({ home_page: normalizedHome }).forEach(({ value, label }) => {
        const opt = document.createElement('option');
        opt.value = value;
        opt.textContent = label;
        if (value === normalizedHome) opt.selected = true;
        homeSelect.appendChild(opt);
    });
    form.appendChild(homeSelect);

    // Buttons
    const btnRow = document.createElement('div');
    btnRow.className = 'profile-edit-buttons';
    btnRow.style.marginTop = '12px';

    const saveBtn = document.createElement('button');
    saveBtn.className = 'profile-create-btn';
    saveBtn.textContent = 'Save';
    saveBtn.onclick = async () => {
        const newName = nameInput.value.trim();
        if (!newName) { alert('Name cannot be empty'); return; }
        try {
            const res = await fetch(`/api/profiles/${currentProfile.id}`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ name: newName, home_page: homeSelect.value || null })
            });
            const data = await res.json();
            if (data.success) {
                currentProfile.name = newName;
                currentProfile.home_page = homeSelect.value || null;
                updateProfileIndicator();
                closeSelfEdit();
                hideProfilePicker();
            } else {
                alert(data.error || 'Failed to update');
            }
        } catch (e) {
            alert('Connection error');
        }
    };
    btnRow.appendChild(saveBtn);

    const cancelBtn = document.createElement('button');
    cancelBtn.className = 'profile-picker-cancel';
    cancelBtn.textContent = 'Cancel';
    cancelBtn.onclick = () => closeSelfEdit();
    btnRow.appendChild(cancelBtn);

    form.appendChild(btnRow);
    container.appendChild(form);

    function closeSelfEdit() {
        form.remove();
        grid.style.display = '';
        actions.style.display = '';
    }
}

async function checkAdminPinRequired() {
    const res = await fetch('/api/profiles');
    const data = await res.json();
    const profiles = data.profiles || [];
    const admin = profiles.find(p => p.is_admin);
    const section = document.getElementById('admin-pin-section');

    if (profiles.length > 1 && admin && !admin.has_pin && section) {
        section.style.display = '';
    } else if (section) {
        section.style.display = 'none';
    }
}

// Service worker registration. Runs as soon as the JS parses (doesn't
// need to wait for DOMContentLoaded). Cache-first image strategy +
// stale-while-revalidate static shell — see /sw.js for details. Skipped
// when the API isn't available (older browsers, file:// origin) or when
// the page is loaded from a non-secure origin (SW requires HTTPS or
// localhost).
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/sw.js', { scope: '/' })
            .catch((err) => console.warn('[SW] registration failed:', err));
    });
}

document.addEventListener('DOMContentLoaded', async function () {
    console.log('SoulSync WebUI initializing...');

    // Check if first-run setup wizard should be shown
    const params = new URLSearchParams(window.location.search);
    const forceSetup = params.get('setup') === '1';
    let showWizard = forceSetup;

    if (!forceSetup) {
        try {
            const setupResp = await fetch('/api/setup/status');
            const setupData = await setupResp.json();
            if (!setupData.setup_complete) {
                showWizard = true;
                localStorage.removeItem('soulsync_setup_complete');
            }
        } catch (e) {
            console.warn('Setup status check failed, continuing normal init:', e);
        }
    }

    if (showWizard && typeof openSetupWizard === 'function') {
        window._onSetupWizardComplete = function () {
            _continueAppInit();
        };
        openSetupWizard();
        return; // Defer init until wizard closes
    }

    _continueAppInit();
});

async function _continueAppInit() {
    // Initialize profile management UI handlers
    initProfileManagement();

    // Check profiles first — may show picker instead of app
    const profileReady = await initProfileSystem();
    if (!profileReady) {
        console.log('Waiting for profile selection...');
        return; // App init deferred until profile is selected via picker
    }

    initApp();
}

function initApp() {
    // Initialize components
    initializeNavigation();
    initializeMobileNavigation();
    initializeMediaPlayer();
    initExpandedPlayer();
    initializeSyncPage();
    initializeWatchlist();
    if (typeof initializeSpotifyAuthCompletionListener === 'function') {
        initializeSpotifyAuthCompletionListener();
    }


    // Initialize WebSocket connection (falls back to HTTP polling if unavailable)
    initializeWebSocket();

    // Start global service status polling for sidebar (works on all pages)
    // Initial fetch for immediate data, then setInterval as fallback when WebSocket is disconnected
    fetchAndUpdateServiceStatus();
    setInterval(fetchAndUpdateServiceStatus, 5000); // Every 5 seconds (no-op when WebSocket active)

    // Check for updates on load and every hour
    checkForUpdates();
    setInterval(checkForUpdates, 3600000);

    // Refresh key data immediately when user returns to this tab
    document.addEventListener('visibilitychange', () => {
        if (!document.hidden) {
            fetchAndUpdateServiceStatus();
            // Refresh dashboard-specific data if on dashboard
            const dashboardPage = document.getElementById('dashboard-page');
            if (dashboardPage && dashboardPage.classList.contains('active')) {
                fetchAndUpdateSystemStats();
                fetchAndUpdateActivityFeed();
            }
        }
    });

    // Start always-on download polling (batched, minimal overhead)
    startGlobalDownloadPolling();

    // Load initial data
    loadInitialData();

    // Handle window resize to re-check track title scrolling
    window.addEventListener('resize', function () {
        if (currentTrack) {
            const trackTitleElement = document.getElementById('track-title');
            const trackTitle = currentTrack.title || 'Unknown Track';
            setTimeout(() => {
                checkAndEnableScrolling(trackTitleElement, trackTitle);
            }, 100); // Small delay to allow layout to settle
        }
    });

    console.log('SoulSync WebUI initialized successfully!');
}

// ===============================
// NAVIGATION SYSTEM
// ===============================

function initializeNavigation() {
    // Sidebar navigation is now driven by native link navigation.
    // Page activation and active-state styling are synchronized from the
    // current URL by the shell bridge and route controllers.
}

const _DEEPLINK_VALID_PAGES = new Set([
    'dashboard', 'sync', 'search', 'discover', 'automations',
    'library', 'import', 'settings', 'help', 'issues', 'stats', 'watchlist',
    'wishlist', 'active-downloads', 'artist-detail', 'playlist-explorer',
    'hydrabase', 'tools'
]);

function _getPageFromPath() {
    const router = getWebRouter();
    const resolved = router?.resolvePageId?.(window.location.pathname);
    if (resolved) return resolved;

    const path = window.location.pathname.replace(/^\/+|\/+$/g, '');
    if (!path) return 'dashboard';
    const segs = path.split('/');
    const basePage = segs[0];
    if (!_DEEPLINK_VALID_PAGES.has(basePage)) return 'dashboard';
    // Context-dependent pages fall back to a sensible parent
    if (basePage === 'playlist-explorer') return 'library';
    return basePage;
}

function _normalizeArtistDetailSource(source) {
    const value = (source || '').toString().trim().toLowerCase();
    return value || 'library';
}

function buildArtistDetailPath(artistId, source = null) {
    if (!artistId) {
        throw new Error('artistId is required for artist-detail navigation');
    }
    const normalizedSource = _normalizeArtistDetailSource(source);
    return '/artist-detail/' + encodeURIComponent(normalizedSource) + '/' + encodeURIComponent(String(artistId));
}

function parseArtistDetailPath(pathname = window.location.pathname) {
    const segs = String(pathname || '').split('/').filter(Boolean);
    if (segs[0] !== 'artist-detail' || segs.length < 3) return null;

    const source = decodeURIComponent(segs[1] || '');
    const artistId = decodeURIComponent(segs.slice(2).join('/'));
    if (!source || !artistId) return null;

    return {
        artistId,
        source: source.toLowerCase() === 'library' ? null : source,
    };
}

// ===============================
// MOBILE NAVIGATION
// ===============================

function initializeMobileNavigation() {
    const hamburgerBtn = document.getElementById('hamburger-btn');
    const sidebar = document.querySelector('.sidebar');
    const overlay = document.getElementById('mobile-overlay');

    if (!hamburgerBtn || !sidebar || !overlay) return;

    function openMobileNav() {
        sidebar.classList.add('mobile-open');
        hamburgerBtn.classList.add('active');
        overlay.classList.add('active');
        document.body.classList.add('mobile-nav-open');
    }

    function closeMobileNav() {
        sidebar.classList.remove('mobile-open');
        hamburgerBtn.classList.remove('active');
        overlay.classList.remove('active');
        document.body.classList.remove('mobile-nav-open');
    }

    hamburgerBtn.addEventListener('click', () => {
        if (sidebar.classList.contains('mobile-open')) {
            closeMobileNav();
        } else {
            openMobileNav();
        }
    });

    overlay.addEventListener('click', closeMobileNav);

    // Close sidebar on nav button click (mobile only)
    document.querySelectorAll('.nav-button').forEach(btn => {
        btn.addEventListener('click', () => {
            if (window.innerWidth <= 768) {
                closeMobileNav();
            }
        });
    });
}

function initializeWatchlist() {
    // Watchlist button navigates to watchlist page
    const watchlistButton = document.getElementById('watchlist-button');
    if (watchlistButton) {
        watchlistButton.addEventListener('click', () => navigateToPage('watchlist'));
    }

    // Wishlist button: quick check for active download, otherwise navigate to page
    const wishlistButton = document.getElementById('wishlist-button');
    if (wishlistButton) {
        wishlistButton.addEventListener('click', async () => {
            // Fast path: check if we already know about an active wishlist process
            const clientProcess = activeDownloadProcesses['wishlist'];
            if (clientProcess && clientProcess.modalElement && document.body.contains(clientProcess.modalElement)) {
                clientProcess.modalElement.style.display = 'flex';
                WishlistModalState.setVisible();
                return;
            }
            // Slow path: ask the server (with timeout to prevent button feeling dead)
            try {
                const controller = new AbortController();
                const timeout = setTimeout(() => controller.abort(), 2000);
                const resp = await fetch('/api/active-processes', { signal: controller.signal });
                clearTimeout(timeout);
                if (resp.ok) {
                    const data = await resp.json();
                    const serverProcess = (data.active_processes || []).find(p => p.playlist_id === 'wishlist');
                    if (serverProcess) {
                        try {
                            WishlistModalState.clearUserClosed();
                            await rehydrateModal(serverProcess, true);
                        } catch (e) {
                            console.debug('Rehydration failed, navigating to page:', e);
                            navigateToPage('wishlist');
                        }
                        return;
                    }
                }
            } catch (e) {
                // Timeout or network error — just navigate
            }
            navigateToPage('wishlist');
        });
    }

    // Update watchlist count initially
    updateWatchlistButtonCount();

    // Update count every 10 seconds
    setInterval(updateWatchlistButtonCount, 10000);

    console.log('Watchlist system initialized');
}

function navigateToPage(pageId, options = {}) {
    navigationEpoch += 1;

    if (!options.forceReload && pageId === currentPage) return;

    // Permission guard — redirect to home page if not allowed
    if (!isPageAllowed(pageId)) {
        const home = getProfileHomePage();
        if (home !== currentPage && isPageAllowed(home)) {
            navigateToPage(home, options);
        }
        return;
    }

    if (pageId === 'artist-detail' && !options.artistId) {
        return false;
    }

    const router = getWebRouter();
    if (router && !options.skipRouteChange) {
        notifyPageWillChange(pageId);
        const route = router.routeManifest?.find((entry) => entry.pageId === pageId);
        if (route?.kind === 'react') {
            showReactHost(pageId);
            setActivePageChrome(pageId);
        }
        return router.navigateToPage(pageId, {
            replace: options.replace === true,
            artistId: options.artistId,
            artistSource: options.artistSource,
        });
    }

    // Fallback path for initial bootstrap or environments without TanStack routing.
    const route = router?.routeManifest?.find((entry) => entry.pageId === pageId);
    notifyPageWillChange(pageId);
    const legacyPageElement = document.getElementById(`${pageId}-page`);
    if (route?.kind === 'react' || !legacyPageElement) {
        showReactHost(pageId);
        setActivePageChrome(pageId);
    } else {
        activatePage(pageId, { forceReload: options.forceReload === true });
    }

    if (!options.skipPushState) {
        const urlPath = pageId === 'dashboard' ? '/'
            : (pageId === 'artist-detail' && options.artistId) ? buildArtistDetailPath(options.artistId, options.artistSource)
            : '/' + pageId;
        if (window.location.pathname !== urlPath) {
            if (options.replace === true) {
                history.replaceState({ page: pageId }, '', urlPath);
            } else {
                history.pushState({ page: pageId }, '', urlPath);
            }
        }
    }

    return true;
}

async function loadPageData(pageId) {
    try {
        // Stop any active polling when navigating away
        stopDbStatsPolling();
        stopDbUpdatePolling();
        stopWishlistCountPolling();
        stopLogPolling();
        // Stop watchlist/wishlist page timers when navigating away
        if (watchlistCountdownInterval) { clearInterval(watchlistCountdownInterval); watchlistCountdownInterval = null; }
        if (wishlistCountdownInterval) { clearInterval(wishlistCountdownInterval); wishlistCountdownInterval = null; }
        if (typeof _stopNebulaLivePolling === 'function') _stopNebulaLivePolling();
        if (pageId !== 'sync') {
            cleanupBeatportContent();
        }
        switch (pageId) {
            case 'dashboard':
                await loadDashboardData();
                loadDashboardSyncHistory();
                break;
            case 'sync':
                initializeSyncPage();
                await loadSyncData();
                break;
            case 'search':
                initializeSearch();
                initializeSearchModeToggle();
                initializeFilters();
                break;
            case 'active-downloads':
                loadActiveDownloadsPage();
                break;
            case 'library':
                // Check if we should return to artist detail view instead of list
                if (artistDetailPageState.currentArtistId && artistDetailPageState.currentArtistName) {
                    navigateToPage('artist-detail', {
                        artistId: artistDetailPageState.currentArtistId,
                        artistSource: artistDetailPageState.currentArtistSource,
                    });
                    if (!artistDetailPageState.isInitialized) {
                        initializeArtistDetailPage();
                        loadArtistDetailData(artistDetailPageState.currentArtistId, artistDetailPageState.currentArtistName);
                    }
                    // Already initialized — DOM content persists, no reload needed
                } else {
                    if (!libraryPageState.isInitialized) {
                        initializeLibraryPage();
                    }
                    // Already initialized — DOM content persists, no reload needed
                }
                break;
            case 'artist-detail':
                // Artist detail page is entered through the route handoff and legacy navigator.
                break;
            case 'discover':
                if (!discoverPageInitialized) {
                    await loadDiscoverPage();
                    discoverPageInitialized = true;
                }
                // Already initialized — DOM content persists, no reload needed
                break;
            case 'playlist-explorer':
                initExplorer();
                break;
            case 'settings':
                initializeSettings();
                switchSettingsTab('connections');
                await loadSettingsData();
                await loadQualityProfile();
                loadApiKeys();
                loadBlacklistCount();
                break;
            case 'import':
                initializeImportPage();
                break;
            case 'hydrabase':
                // Check connection status and pre-fill saved credentials
                try {
                    const hsResp = await fetch('/api/hydrabase/status');
                    const hsData = await hsResp.json();
                    _hydrabaseConnected = hsData.connected;
                    document.getElementById('hydra-connection-status').textContent = hsData.connected ? 'Connected' : 'Disconnected';
                    document.getElementById('hydra-connection-status').style.color = hsData.connected ? 'rgb(var(--accent-light-rgb))' : '#888';
                    document.getElementById('hydra-connect-btn').textContent = hsData.connected ? 'Disconnect' : 'Connect';
                    // Pre-fill saved credentials
                    if (hsData.saved_url) {
                        document.getElementById('hydra-ws-url').value = hsData.saved_url;
                    }
                    if (hsData.saved_api_key) {
                        document.getElementById('hydra-api-key').value = hsData.saved_api_key;
                    }
                    // Update peer count
                    if (hsData.peer_count !== null && hsData.peer_count !== undefined) {
                        document.getElementById('hydra-peer-count').textContent = `Peers: ${hsData.peer_count}`;
                    }
                } catch (e) { }
                // Load comparisons
                loadHydrabaseComparisons();
                break;
            case 'tools':
                await initializeToolsPage();
                break;
            case 'watchlist':
                await initializeWatchlistPage();
                break;
            case 'wishlist':
                await initializeWishlistPage();
                break;
            case 'automations':
                await loadAutomations();
                break;
            case 'help':
                initializeDocsPage();
                break;
        }
    } catch (error) {
        console.error(`Error loading ${pageId} data:`, error);
        showToast(`Failed to load ${pageId} data`, 'error');
    }
}

// ---- Dashboard cursor-following accent blob (two-layer liquid) ----
// Both layers lerp toward a target point: the cursor when it's hovering
// any .dash-card, otherwise the grid center (idle resting position).
// Core layer (--blob-x/y) follows faster, halo (--blob-x-soft/y-soft)
// trails. Each card renders both layers and clips them to its own bounds
// via overflow:hidden, so the blob spans the bento while gaps stay dark.
// Disabled entirely when body.reduce-effects is set.
(function initDashboardCursorBlob() {
    let grid = null;
    let cards = [];
    let cardRects = [];               // cached rects, refreshed each frame
    let targetX = 0, targetY = 0;
    let coreX = 0, coreY = 0;
    let softX = 0, softY = 0;
    let rafId = 0;
    let attached = false;
    let centeredOnce = false;

    const RECENTER_DELAY_MS = 1500;
    let recenterTimer = 0;

    const isReduced = () => document.body.classList.contains('reduce-effects');

    const gridCenter = () => {
        const r = grid.getBoundingClientRect();
        return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
    };

    // Two-pass per frame: read all rects first (one layout flush), then
    // write all CSS vars (no further reads). Avoids per-card layout thrash.
    const tick = () => {
        if (isReduced()) { rafId = 0; return; }

        coreX += (targetX - coreX) * 0.040;
        coreY += (targetY - coreY) * 0.040;
        softX += (targetX - softX) * 0.022;
        softY += (targetY - softY) * 0.022;

        const n = cards.length;
        if (cardRects.length !== n) cardRects.length = n;
        for (let i = 0; i < n; i++) cardRects[i] = cards[i].getBoundingClientRect();
        for (let i = 0; i < n; i++) {
            const r = cardRects[i];
            const s = cards[i].style;
            s.setProperty('--blob-x',      (coreX - r.left) + 'px');
            s.setProperty('--blob-y',      (coreY - r.top)  + 'px');
            s.setProperty('--blob-x-soft', (softX - r.left) + 'px');
            s.setProperty('--blob-y-soft', (softY - r.top)  + 'px');
        }

        const dx = Math.abs(targetX - softX) + Math.abs(targetX - coreX);
        const dy = Math.abs(targetY - softY) + Math.abs(targetY - coreY);
        if (dx + dy > 0.4) rafId = requestAnimationFrame(tick);
        else rafId = 0;
    };

    const ensureLoop = () => {
        if (!rafId && !isReduced()) rafId = requestAnimationFrame(tick);
    };

    const cancelRecenter = () => {
        if (recenterTimer) { clearTimeout(recenterTimer); recenterTimer = 0; }
    };
    const recenterNow = () => {
        recenterTimer = 0;
        if (!grid) return;
        const c = gridCenter();
        targetX = c.x; targetY = c.y;
        ensureLoop();
    };
    const scheduleRecenter = () => {
        if (recenterTimer) return;
        recenterTimer = setTimeout(recenterNow, RECENTER_DELAY_MS);
    };

    // Snap the blob to grid center the first time the grid becomes
    // measurable (page may not be visible at DOMContentLoaded).
    const snapToCenterIfReady = () => {
        if (!grid || centeredOnce) return;
        const r = grid.getBoundingClientRect();
        if (r.width === 0 || r.height === 0) return;  // not visible yet
        const c = { x: r.left + r.width / 2, y: r.top + r.height / 2 };
        targetX = coreX = softX = c.x;
        targetY = coreY = softY = c.y;
        centeredOnce = true;
        ensureLoop();
    };

    function attach() {
        if (attached) return;
        grid = document.querySelector('.dash-grid');
        if (!grid) return;
        attached = true;
        cards = Array.from(grid.querySelectorAll('.dash-card'));

        snapToCenterIfReady();

        grid.addEventListener('pointermove', (e) => {
            if (isReduced()) return;
            const onCard = e.target && e.target.closest && e.target.closest('.dash-card');
            if (onCard) {
                cancelRecenter();
                targetX = e.clientX;
                targetY = e.clientY;
                ensureLoop();
            } else {
                scheduleRecenter();
            }
        });
        grid.addEventListener('pointerleave', () => {
            if (!isReduced()) scheduleRecenter();
        });
        window.addEventListener('resize', () => {
            if (isReduced()) return;
            // Idle: snap immediately. Active: respect the existing delay.
            if (!recenterTimer) recenterNow();
        });

        // Re-resolve cards when active-downloads card toggles visibility.
        const cardObserver = new MutationObserver(() => {
            cards = Array.from(grid.querySelectorAll('.dash-card'));
            ensureLoop();
        });
        cardObserver.observe(grid, { childList: true, subtree: false, attributes: true, attributeFilter: ['style', 'class'] });

        // If the grid was hidden at attach time, snap once it becomes
        // measurable (page navigation, tab switch).
        if (!centeredOnce && 'IntersectionObserver' in window) {
            const visObserver = new IntersectionObserver((entries) => {
                for (const ent of entries) {
                    if (ent.isIntersecting) { snapToCenterIfReady(); break; }
                }
            });
            visObserver.observe(grid);
        }

        // React to reduce-effects toggle on body class.
        const bodyObserver = new MutationObserver(() => {
            if (isReduced()) {
                cancelRecenter();
                if (rafId) { cancelAnimationFrame(rafId); rafId = 0; }
            } else {
                centeredOnce = false;
                snapToCenterIfReady();
            }
        });
        bodyObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', attach);
    } else {
        attach();
    }
    // Also retry on full load — covers late-mounted markup.
    window.addEventListener('load', () => {
        attach();
        snapToCenterIfReady();
    });
})();
