Motherless.com - Advanced Gallery Sorter

Sorts videos on motherless.com with an advanced, panel-based UI. Supports Title, Views, Duration, Uploader, Random, and "Deep Sort" options (Upload Date, Favorites, Comments, Resolution, Engagement). Fetches all gallery pages for comprehensive sorting and provides pagination. Features a sleek dark mode UI.

// ==UserScript==
// @name         Motherless.com - Advanced Gallery Sorter
// @namespace    http://tampermonkey.net/
// @version      3.4
// @description  Sorts videos on motherless.com with an advanced, panel-based UI. Supports Title, Views, Duration, Uploader, Random, and "Deep Sort" options (Upload Date, Favorites, Comments, Resolution, Engagement). Fetches all gallery pages for comprehensive sorting and provides pagination. Features a sleek dark mode UI.
// @author       baratheonblight75
// @match        *://*.motherless.com/*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

(function($) {
    'use strict';

    const VIDEO_ITEM_SELECTOR = 'div.thumb-container.video';
    const VIDEOS_PER_PAGE_DISPLAY = 2500;
    const MAX_CONCURRENT_DEEP_FETCHES = 5;
    const DEFAULT_SORT_ID = 'views';
    const DEFAULT_SORT_DIR = 'desc';

    // --- SVG Icons ---
    const SVG_ICONS = {
        filter: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 6l8 0" /><path d="M16 6l4 0" /><path d="M8 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 12l2 0" /><path d="M10 12l10 0" /><path d="M17 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 18l11 0" /><path d="M19 18l1 0" /></svg>',
        close: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 6l-12 12" /><path d="M6 6l12 12" /></svg>',
        title: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M16 8h4l-4 8h4" /><path d="M4 16v-6a2 2 0 1 1 4 0v6" /><path d="M4 13h4" /><path d="M11 12h2" /></svg>',
        views: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /></svg>',
        duration: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 12l3 2" /><path d="M12 7v5" /></svg>',
        uploader: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg>',
        uploadDate: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M11.795 21h-6.795a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v4" /><path d="M18 18m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M15 3v4" /><path d="M7 3v4" /><path d="M3 11h16" /><path d="M18 16.496v1.504l1 1" /></svg>',
        favorites: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" /></svg>',
        comments: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 9h8" /><path d="M8 13h6" /><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12z" /></svg>',
        resolution: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M14 9v6h1a2 2 0 0 0 2 -2v-2a2 2 0 0 0 -2 -2h-1z" /><path d="M7 15v-6" /><path d="M10 15v-6" /><path d="M7 12h3" /></svg>',
        random: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 4l3 3l-3 3" /><path d="M18 20l3 -3l-3 -3" /><path d="M3 7h3a5 5 0 0 1 5 5a5 5 0 0 0 5 5h5" /><path d="M21 7h-5a4.978 4.978 0 0 0 -3 1m-4 8a4.984 4.984 0 0 1 -3 1h-3" /></svg>',
        arrowUp: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 5l0 14" /><path d="M18 11l-6 -6" /><path d="M6 11l6 -6" /></svg>',
        arrowDown: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 5l0 14" /><path d="M18 13l-6 6" /><path d="M6 13l6 6" /></svg>'
    };

    // --- Sort Definitions ---
    const SORT_DEFINITIONS = [
        { group: "Basic Sorts", id: 'title', label: 'Title', svgIcon: SVG_ICONS.title, defaultDir: 'asc', deepScan: false, keys: { asc: 'titleAZ', desc: 'titleZA' } },
        { group: "Basic Sorts", id: 'views', label: 'Views', svgIcon: SVG_ICONS.views, defaultDir: 'desc', deepScan: false, keys: { asc: 'viewsLeast', desc: 'viewsMost' } },
        { group: "Basic Sorts", id: 'duration', label: 'Duration', svgIcon: SVG_ICONS.duration, defaultDir: 'desc', deepScan: false, keys: { asc: 'durationShortest', desc: 'durationLongest' } },
        { group: "Basic Sorts", id: 'uploader', label: 'Uploader', svgIcon: SVG_ICONS.uploader, defaultDir: 'asc', deepScan: false, keys: { asc: 'uploaderAZ', desc: 'uploaderZA' } },
        { group: "Advanced Sorts (Deep Scan)", id: 'uploadDate', label: 'Upload Date', svgIcon: SVG_ICONS.uploadDate, defaultDir: 'desc', deepScan: true, keys: { asc: 'deep:uploadDateOldest', desc: 'deep:uploadDateNewest' } },
        { group: "Advanced Sorts (Deep Scan)", id: 'favorites', label: 'Favorites', svgIcon: SVG_ICONS.favorites, defaultDir: 'desc', deepScan: true, keys: { asc: 'deep:favoritesLeast', desc: 'deep:favoritesMost' } },
        { group: "Advanced Sorts (Deep Scan)", id: 'comments', label: 'Comments', svgIcon: SVG_ICONS.comments, defaultDir: 'desc', deepScan: true, keys: { asc: 'deep:commentsLeast', desc: 'deep:commentsMost' } },
        { group: "Advanced Sorts (Deep Scan)", id: 'resolution', label: 'Resolution', svgIcon: SVG_ICONS.resolution, defaultDir: 'desc', deepScan: true, keys: { asc: 'deep:resolutionLowest', desc: 'deep:resolutionHighest' } },
        {
            group: "Engagement Sorts (Deep Scan)", id: 'engagementComments', label: 'Comments/View',
            svgMain: SVG_ICONS.comments, svgBadge: SVG_ICONS.views,
            defaultDir: 'desc', deepScan: true,
            keys: { asc: 'deep:engagementCommentsLeast', desc: 'deep:engagementCommentsMost' }
        },
        {
            group: "Engagement Sorts (Deep Scan)", id: 'engagementFavorites', label: 'Favorites/View',
            svgMain: SVG_ICONS.favorites, svgBadge: SVG_ICONS.views,
            defaultDir: 'desc', deepScan: true,
            keys: { asc: 'deep:engagementFavoritesLeast', desc: 'deep:engagementFavoritesMost' }
        },
        { group: "Other", id: 'random', label: 'Random', svgIcon: SVG_ICONS.random, noDirection: true, deepScan: false, key: 'random' }
    ];

    // --- Helper Functions ---
    function parseViews(text) {
        if (typeof text !== 'string') return 0;
        text = text.trim().toLowerCase();
        let multiplier = 1;
        if (text.includes('k')) multiplier = 1000;
        if (text.includes('m')) multiplier = 1000000;
        let number = parseFloat(text.replace(/[^0-9.]/g, ''));
        return isNaN(number) ? 0 : Math.round(number * multiplier);
    }

    function parseDurationToSeconds(durationStr) {
        if (!durationStr || typeof durationStr !== 'string') return 0;
        const parts = durationStr.trim().split(':').map(part => parseInt(part, 10));
        let seconds = 0;
        const validParts = parts.filter(p => !isNaN(p));
        if (validParts.length === 2) seconds = validParts[0] * 60 + validParts[1];
        else if (validParts.length === 3) seconds = validParts[0] * 3600 + validParts[1] * 60 + validParts[2];
        else if (validParts.length === 1) seconds = validParts[0];
        return isNaN(seconds) ? 0 : seconds;
    }

    function shuffleArray(array) {
        for (let i = array.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [array[i], array[j]] = [array[j], array[i]];
        }
    }

    // --- Deep Data Parsing Functions ---
    function parseUploadDate(dateStr) {
        if (!dateStr || typeof dateStr !== 'string') return null;
        try { const date = new Date(dateStr.trim()); return isNaN(date.getTime()) ? null : date; }
        catch (e) { console.warn("Could not parse date:", dateStr, e); return null; }
    }
    function parseFavorites(favStr) {
        if (!favStr || typeof favStr !== 'string') return null;
        const num = parseInt(favStr.replace(/[^0-9]/g, ''), 10); return isNaN(num) ? null : num;
    }
    function parseComments(commentStr) {
        if (!commentStr || typeof commentStr !== 'string') return null;
        const num = parseInt(commentStr.replace(/[^0-9]/g, ''), 10); return isNaN(num) ? null : num;
    }
    function parseResolution(resStr) {
        if (!resStr || typeof resStr !== 'string') return null;
        const num = parseInt(resStr.replace(/[^0-9]/g, ''), 10); return isNaN(num) ? null : num;
    }

    // --- UI Styling ---
    const newStyles = `
    :root {
        --mvs-bg-deep: #1A1A1A;
        --mvs-bg-panel: #282828;
        --mvs-bg-header-footer: #333333;
        --mvs-bg-element: #3C3C3C;
        --mvs-bg-hover: #454545;
        --mvs-bg-active: #505050;
        --mvs-border-strong: #555555;
        --mvs-border-medium: #4A4A4A;
        --mvs-border-light: #404040;
        --mvs-text-primary: #E0E0E0;
        --mvs-text-secondary: #AAAAAA;
        --mvs-text-disabled: #777777;
        --mvs-accent-primary: #606060;
        --mvs-accent-hover: #707070;
    }

    #mvs-sort-toggle-btn {
        position: fixed;
        bottom: 20px;
        right: 20px;
        z-index: 100000;
        background-color: var(--mvs-accent-primary);
        color: var(--mvs-text-primary);
        border: none;
        padding: 0;
        width: 52px;
        height: 52px;
        border-radius: 50%;
        cursor: pointer;
        box-shadow: 0 3px 10px rgba(0,0,0,0.5);
        display: flex;
        align-items: center;
        justify-content: center;
        transition: background-color 0.2s, transform 0.2s, box-shadow 0.2s;
    }
    #mvs-sort-toggle-btn:hover {
        background-color: var(--mvs-accent-hover);
        transform: translateY(-2px);
        box-shadow: 0 5px 12px rgba(0,0,0,0.6);
    }
    #mvs-sort-toggle-btn svg {
        width: 28px;
        height: 28px;
        stroke: currentColor;
    }

    #mvs-sort-panel-container {
        position: fixed;
        bottom: 85px;
        right: 20px;
        width: 280px; /* Further reduced width for "squished" look */
        max-height: auto; /* Height will be determined by content */
        background-color: var(--mvs-bg-panel);
        color: var(--mvs-text-primary);
        border: 1px solid var(--mvs-border-medium);
        border-radius: 8px; /* Slightly less rounded for compact look */
        box-shadow: 0 4px 15px rgba(0,0,0,0.35);
        z-index: 99999;
        font-family: 'Segoe UI', Arial, sans-serif;
        display: none;
        flex-direction: column;
        overflow: hidden;
    }
    .mvs-panel-header {
        padding: 10px 15px; /* Reduced padding */
        background-color: var(--mvs-bg-header-footer);
        /* No border-bottom needed if panel body is gone and footer is next */
        display: flex;
        flex-direction: column;
        gap: 8px; /* Reduced gap */
        align-items: flex-start;
    }
    .mvs-panel-header-top {
        display: flex;
        justify-content: space-between;
        align-items: center;
        width: 100%;
    }
    .mvs-panel-header h3 {
        margin: 0;
        font-size: 15px;
        font-weight: 600;
        color: var(--mvs-text-primary);
    }
    .mvs-panel-close-btn {
        background: none;
        border: none;
        color: var(--mvs-text-secondary);
        cursor: pointer;
        padding: 5px;
        line-height: 1;
        border-radius: 50%;
        transition: background-color 0.15s, color 0.15s;
    }
    .mvs-panel-close-btn:hover {
        color: var(--mvs-text-primary);
        background-color: var(--mvs-bg-hover);
    }
    .mvs-panel-close-btn svg {
        width: 18px; /* Smaller close icon */
        height: 18px;
        stroke: currentColor;
    }
    #mvs-primary-sort-select {
        width: 100%;
        padding: 8px 10px; /* Reduced padding */
        background-color: var(--mvs-bg-element);
        color: var(--mvs-text-primary);
        border: 1px solid var(--mvs-border-medium);
        border-radius: 5px; /* Reduced radius */
        font-size: 13px; /* Reduced font size */
        -webkit-appearance: none;
        -moz-appearance: none;
        appearance: none;
        background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23E0E0E0%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.4-5.4-13z%22%2F%3E%3C%2Fsvg%3E');
        background-repeat: no-repeat;
        background-position: right 8px center;
        background-size: 10px 10px;
        padding-right: 30px;
        transition: border-color 0.15s, background-color 0.15s;
    }
    #mvs-primary-sort-select:hover {
        border-color: var(--mvs-border-strong);
    }
    #mvs-primary-sort-select option {
        background-color: var(--mvs-bg-element);
        color: var(--mvs-text-primary);
        font-size: 13px; /* Ensure option font size matches select */
    }
    #mvs-primary-sort-select optgroup {
        background-color: var(--mvs-bg-header-footer);
        color: var(--mvs-text-secondary);
        font-style: italic;
        font-weight: normal;
    }
    #mvs-primary-sort-select optgroup option {
        background-color: var(--mvs-bg-element);
        color: var(--mvs-text-primary);
        font-style: normal;
    }

    .mvs-panel-body {
        display: none; /* Hide panel body as it's no longer used */
        padding: 0;
    }

    .mvs-sort-direction-controls { /* This is inside .mvs-panel-header in HTML */
        display: flex;
        align-items: center;
        margin-left: 0;
        margin-top: 4px; /* Reduced top margin */
        background-color: var(--mvs-bg-element);
        border-radius: 5px; /* Reduced radius */
        padding: 3px; /* Reduced padding */
        border: 1px solid var(--mvs-border-medium);
    }
    .mvs-dir-btn {
        flex-grow: 1;
        background: transparent;
        border: none;
        color: var(--mvs-text-secondary);
        padding: 5px; /* Reduced padding */
        margin: 0 1px; /* Reduced margin */
        border-radius: 3px; /* Reduced radius */
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: background-color 0.15s, color 0.15s;
    }
    .mvs-dir-btn:hover {
        color: var(--mvs-text-primary);
        background-color: var(--mvs-bg-hover);
    }
    .mvs-dir-btn.mvs-active-dir {
        background-color: var(--mvs-accent-primary);
        color: var(--mvs-text-primary);
    }
    .mvs-dir-btn svg {
        width: 16px; /* Reduced icon size */
        height: 16px;
        stroke: currentColor;
    }
    .mvs-panel-footer {
        padding: 10px 15px; /* Reduced padding */
        background-color: var(--mvs-bg-header-footer);
        border-top: 1px solid var(--mvs-border-medium); /* Keep top border for separation */
        display: flex;
        justify-content: space-between; /* Space out buttons for compact footer */
        gap: 8px;
    }
    .mvs-panel-action-btn {
        padding: 8px 12px; /* Reduced padding */
        flex-grow: 1; /* Make buttons take equal space in footer */
        text-align: center; /* Center text in button */
        border-radius: 5px; /* Reduced radius */
        font-size: 12px; /* Reduced font size */
        font-weight: 600;
        cursor: pointer;
        border: none;
        color: var(--mvs-text-primary);
        transition: background-color 0.2s, transform 0.1s;
    }
    .mvs-panel-action-btn:active {
        transform: scale(0.98);
    }
    .mvs-panel-action-btn.mvs-apply-btn {
        background-color: var(--mvs-accent-primary);
    }
    .mvs-panel-action-btn.mvs-apply-btn:hover {
        background-color: var(--mvs-accent-hover);
    }
    .mvs-panel-action-btn.mvs-clear-btn {
        background-color: var(--mvs-bg-element);
    }
    .mvs-panel-action-btn.mvs-clear-btn:hover {
        background-color: var(--mvs-bg-hover);
    }

    #loading-indicator {
        background-color: rgba(30, 30, 30, 0.9) !important;
        color: var(--mvs-text-primary) !important;
        padding: 25px 30px !important;
        border-radius: 8px !important;
        border: 1px solid var(--mvs-border-medium) !important;
        box-shadow: 0 4px 15px rgba(0,0,0,0.5) !important;
        font-family: 'Segoe UI', Arial, sans-serif !important;
        font-size: 18px !important;
        font-weight: 500 !important;
    }
    #pagination-controls {
        background-color: var(--mvs-bg-header-footer) !important;
        color: var(--mvs-text-primary) !important;
        padding: 10px 15px !important;
        border: 1px solid var(--mvs-border-medium) !important;
        border-radius: 6px !important;
        box-shadow: 0 -2px 8px rgba(0,0,0,0.3) !important;
        font-family: 'Segoe UI', Arial, sans-serif !important;
        font-size: 14px !important;
        bottom: 20px !important;
    }
    #pagination-controls button {
        background-color: var(--mvs-bg-element);
        color: var(--mvs-text-primary);
        border: 1px solid var(--mvs-border-medium);
        padding: 7px 14px;
        margin: 0 5px;
        cursor: pointer;
        border-radius: 5px;
        font-family: 'Segoe UI', Arial, sans-serif;
        font-size: 13px;
        font-weight: 500;
        transition: background-color 0.15s, border-color 0.15s;
    }
    #pagination-controls button:hover:not(:disabled) {
        background-color: var(--mvs-bg-hover);
        border-color: var(--mvs-border-strong);
    }
    #pagination-controls button:disabled {
        background-color: var(--mvs-bg-header-footer);
        color: var(--mvs-text-disabled);
        cursor: default;
        border-color: var(--mvs-border-light);
    }
    #pagination-controls span {
        color: var(--mvs-text-primary);
        margin: 0 10px;
        font-family: 'Segoe UI', Arial, sans-serif;
        font-size: 14px;
    }
    `;

    function addGlobalStyle(css) {
        var head, style;
        head = document.getElementsByTagName('head')[0];
        if (!head) { return; }
        style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = css;
        head.appendChild(style);
    }

    // --- Sorting Logic (largely unchanged) ---
    function nullSafeSort(valA, valB, ascending = true, aIsBetter = false) {
        const factor = ascending ? 1 : -1;
        const aIsNull = valA === null || valA === undefined;
        const bIsNull = valB === null || valB === undefined;
        if (aIsNull && bIsNull) return 0;
        if (aIsNull) return aIsBetter ? -1 * factor : 1 * factor;
        if (bIsNull) return aIsBetter ? 1 * factor : -1 * factor;
        if (valA < valB) return -1 * factor;
        if (valA > valB) return 1 * factor;
        return 0;
    }

    const Sorters = {
        'titleAZ': (a, b) => (a.title || '').localeCompare(b.title || ''),
        'titleZA': (a, b) => (b.title || '').localeCompare(a.title || ''),
        'viewsMost': (a, b) => nullSafeSort(a.views, b.views, false),
        'viewsLeast': (a, b) => nullSafeSort(a.views, b.views, true),
        'durationLongest': (a, b) => nullSafeSort(a.duration, b.duration, false),
        'durationShortest': (a, b) => nullSafeSort(a.duration, b.duration, true),
        'uploaderAZ': (a, b) => (a.uploader || '').localeCompare(b.uploader || ''),
        'uploaderZA': (a, b) => (b.uploader || '').localeCompare(a.uploader || ''),
        'deep:uploadDateNewest': (a, b) => nullSafeSort(a.uploadDate ? a.uploadDate.getTime() : null, b.uploadDate ? b.uploadDate.getTime() : null, false),
        'deep:uploadDateOldest': (a, b) => nullSafeSort(a.uploadDate ? a.uploadDate.getTime() : null, b.uploadDate ? b.uploadDate.getTime() : null, true),
        'deep:favoritesMost': (a, b) => nullSafeSort(a.favorites, b.favorites, false),
        'deep:favoritesLeast': (a, b) => nullSafeSort(a.favorites, b.favorites, true),
        'deep:commentsMost': (a, b) => nullSafeSort(a.commentsCount, b.commentsCount, false),
        'deep:commentsLeast': (a, b) => nullSafeSort(a.commentsCount, b.commentsCount, true),
        'deep:resolutionHighest': (a, b) => nullSafeSort(a.resolution, b.resolution, false),
        'deep:resolutionLowest': (a, b) => nullSafeSort(a.resolution, b.resolution, true),
        'deep:engagementCommentsMost': (a,b) => {
            const valA = (a.views > 0 && a.commentsCount !== null) ? a.commentsCount / a.views : null;
            const valB = (b.views > 0 && b.commentsCount !== null) ? b.commentsCount / b.views : null;
            return nullSafeSort(valA, valB, false);
        },
        'deep:engagementCommentsLeast': (a,b) => {
            const valA = (a.views > 0 && a.commentsCount !== null) ? a.commentsCount / a.views : null;
            const valB = (b.views > 0 && b.commentsCount !== null) ? b.commentsCount / b.views : null;
            return nullSafeSort(valA, valB, true);
        },
        'deep:engagementFavoritesMost': (a,b) => {
            const valA = (a.views > 0 && a.favorites !== null) ? a.favorites / a.views : null;
            const valB = (b.views > 0 && b.favorites !== null) ? b.favorites / b.views : null;
            return nullSafeSort(valA, valB, false);
        },
        'deep:engagementFavoritesLeast': (a,b) => {
            const valA = (a.views > 0 && a.favorites !== null) ? a.favorites / a.views : null;
            const valB = (b.views > 0 && b.favorites !== null) ? b.favorites / b.views : null;
            return nullSafeSort(valA, valB, true);
        },
    };

    function applySorting(dataArray, sortKey) {
        if (sortKey === 'random') {
            shuffleArray(dataArray);
        } else if (Sorters[sortKey]) {
            dataArray.sort(Sorters[sortKey]);
        } else if (sortKey === 'NO_SORT') {
            // Do nothing, keep original order (assuming dataArray is already in original order)
        } else {
            console.warn(`Unknown sort key: ${sortKey}. Defaulting to viewsMost.`);
            dataArray.sort(Sorters['viewsMost']);
        }
    }

    // --- UI & Pagination (largely unchanged logic, but invocation changes) ---
    function displayPage($container, videoData, page, videosPerPage) {
        $container.empty();
        const start = (page - 1) * videosPerPage;
        const end = Math.min(start + videosPerPage, videoData.length);
        for (let i = start; i < end; i++) {
            if (videoData[i] && videoData[i].element) {
                $container.append(videoData[i].element);
            }
        }
        updatePaginationControls($container, videoData, page, videosPerPage);
    }

    function updatePaginationControls($container, videoData, currentPage, videosPerPage) {
        const totalVideos = videoData.length;
        const totalPages = Math.ceil(totalVideos / videosPerPage);
        let $pagination = $('#pagination-controls');
        if ($pagination.length) $pagination.remove();
        $pagination = $('<div id="pagination-controls"></div>').css({
            position: 'fixed', left: '50%', transform: 'translateX(-50%)',
            zIndex: 9999, textAlign: 'center'
        });
        if (totalPages <= 1) {
            if (totalVideos > 0) $pagination.text(`Total videos: ${totalVideos}`);
            else $pagination.text('');
            $('body').append($pagination); return;
        }
        const $prev = $('<button>Previous</button>');
        const $next = $('<button>Next</button>');
        const $pageInfo = $('<span></span>').text(` Page ${currentPage} of ${totalPages} (${totalVideos} videos) `);
        if (currentPage === 1) $prev.prop('disabled', true);
        if (currentPage === totalPages) $next.prop('disabled', true);
        $prev.click(() => displayPage($container, videoData, currentPage - 1, videosPerPage));
        $next.click(() => displayPage($container, videoData, currentPage + 1, videosPerPage));
        $pagination.append($prev, $pageInfo, $next);
        $('body').append($pagination);
    }

    // --- Data Extraction (unchanged) ---
    function extractVideoData($videoElement) {
        const title = ($videoElement.find('a.caption.title').text() || '').trim();
        const uploader = ($videoElement.find('a.uploader').text() || '').trim();
        const viewsText = ($videoElement.find('span.hits span.value').text() || '0').trim();
        const durationText = ($videoElement.find('a.img-container span.size').text() || '00:00').trim();
        const pageUrlAnchor = $videoElement.find('a.img-container[href], a.caption.title[href]').first();
        let pageUrl = pageUrlAnchor.attr('href');
        if (pageUrl && !pageUrl.startsWith('http')) {
            try { pageUrl = new URL(pageUrl, window.location.origin).href; }
            catch (e) { console.error("Error constructing absolute URL:", pageUrl, e); pageUrl = null; }
        }
        return {
            element: $videoElement.clone(), title: title, uploader: uploader,
            views: parseViews(viewsText), duration: parseDurationToSeconds(durationText),
            pageUrl: pageUrl, uploadDate: null, favorites: null, commentsCount: null,
            resolution: null, deepDataFetched: false, deepDataError: false, originalIndex: -1 // For potential revert
        };
    }
    
    let initialVideoDataCache = []; // Cache for "Clear Sort" to revert to original page 1

    // Promisified GM_xmlhttpRequest (unchanged)
    function GM_fetch(details) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                ...details,
                onload: function(response) {
                    if (response.status >= 200 && response.status < 300) resolve(response);
                    else reject(new Error(`Request failed: ${response.status} ${response.statusText} for ${details.url}`));
                },
                onerror: function(response) { reject(new Error(`Request error: ${response.statusText} for ${details.url}`)); },
                ontimeout: function() { reject(new Error(`Request timed out for ${details.url}`)); }
            });
        });
    }

    // fetchDeepVideoDetails and related orchestration (unchanged logic)
    async function fetchDeepVideoDetails(videoObject) {
        if (!videoObject.pageUrl) {
            videoObject.deepDataError = true;
            return Promise.reject("No pageUrl");
        }
        try {
            const response = await GM_fetch({ method: "GET", url: videoObject.pageUrl, timeout: 15000 });
            const parser = new DOMParser();
            const doc = parser.parseFromString(response.responseText, "text/html");
            const dateStr = $(doc).find('#media-info .media-meta-stats span.count:nth-of-type(3)').text();
            const favStr = $(doc).find('#media-info .media-meta-stats span.count:nth-of-type(2)').text();
            const commentStr = $(doc).find('#media-comments .comments-count span.count').text();
            const videoElem = $(doc).find('video[id^="ml-video-"]').first();
            const qualityStr = videoElem.length ? videoElem.attr('data-quality') : null;
            videoObject.uploadDate = parseUploadDate(dateStr);
            videoObject.favorites = parseFavorites(favStr);
            videoObject.commentsCount = parseComments(commentStr);
            videoObject.resolution = parseResolution(qualityStr);
            videoObject.deepDataFetched = true;
            videoObject.deepDataError = false;
        } catch (error) {
            console.error(`Failed to fetch or parse deep data for ${videoObject.pageUrl}:`, error);
            videoObject.deepDataError = true;
            throw error;
        }
    }

    let activeDeepFetches = 0;
    let deepFetchQueue = [];
    let allVideoDataForSort = []; // Renamed for clarity, holds all videos after gallery fetch
    let currentSortKeyBeingApplied = '';
    let currentContainerForDisplay = null;
    let currentLoadingIndicator = null;

    function updateDeepFetchProgress(processedCount, totalToFetch) {
        if (currentLoadingIndicator && currentLoadingIndicator.length) {
            currentLoadingIndicator.text(`Fetching video details: ${processedCount}/${totalToFetch}...`);
        }
    }

    function processNextInDeepFetchQueue() {
        if (deepFetchQueue.length === 0 && activeDeepFetches === 0) {
            if (currentLoadingIndicator) currentLoadingIndicator.text('Sorting...');
            applySorting(allVideoDataForSort, currentSortKeyBeingApplied);
            if (currentLoadingIndicator) currentLoadingIndicator.remove(); currentLoadingIndicator = null;
            if (currentContainerForDisplay) {
                displayPage(currentContainerForDisplay, allVideoDataForSort, 1, VIDEOS_PER_PAGE_DISPLAY);
            }
            return;
        }
        while (activeDeepFetches < MAX_CONCURRENT_DEEP_FETCHES && deepFetchQueue.length > 0) {
            activeDeepFetches++;
            const { video, totalToFetch } = deepFetchQueue.shift();
            fetchDeepVideoDetails(video)
                .catch(err => { /* Error handled */ })
                .finally(() => {
                    activeDeepFetches--;
                    const processedCount = totalToFetch - deepFetchQueue.length - activeDeepFetches;
                    updateDeepFetchProgress(processedCount, totalToFetch);
                    processNextInDeepFetchQueue();
                });
        }
    }

    function initiateDeepSortProcess(sortKeyToApply) {
        const itemsToFetch = allVideoDataForSort.filter(v => !v.deepDataFetched && !v.deepDataError && v.pageUrl);
        if (itemsToFetch.length === 0) {
            if (currentLoadingIndicator) currentLoadingIndicator.text('Sorting...');
            applySorting(allVideoDataForSort, sortKeyToApply);
            if (currentLoadingIndicator) currentLoadingIndicator.remove(); currentLoadingIndicator = null;
            displayPage(currentContainerForDisplay, allVideoDataForSort, 1, VIDEOS_PER_PAGE_DISPLAY);
            return;
        }
        deepFetchQueue = itemsToFetch.map(video => ({ video: video, totalToFetch: itemsToFetch.length }));
        updateDeepFetchProgress(0, itemsToFetch.length);
        processNextInDeepFetchQueue();
    }

    // --- Main Sorting and Pagination Process ---
    function prepareAndSortGallery($container, $initialVideosOnPage, sortOptionKey) {
        currentSortKeyBeingApplied = sortOptionKey;
        currentContainerForDisplay = $container;

        if (currentLoadingIndicator && currentLoadingIndicator.length) currentLoadingIndicator.remove();
        currentLoadingIndicator = $('<div id="loading-indicator">Loading page 1...</div>').css({
            position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', zIndex: 10000
        });
        $('body').append(currentLoadingIndicator);

        if (allVideoDataForSort.length > 0 && !sortOptionKey.startsWith('deep:')) {
            console.log('Using cached gallery data. Total videos:', allVideoDataForSort.length);
            currentLoadingIndicator.text('Sorting...');
            applySorting(allVideoDataForSort, currentSortKeyBeingApplied);
            currentLoadingIndicator.remove(); currentLoadingIndicator = null;
            displayPage(currentContainerForDisplay, allVideoDataForSort, 1, VIDEOS_PER_PAGE_DISPLAY);
            return;
        }
         if (allVideoDataForSort.length > 0 && sortOptionKey.startsWith('deep:')) {
            console.log('Using cached gallery data, initiating deep sort. Total videos:', allVideoDataForSort.length);
            initiateDeepSortProcess(currentSortKeyBeingApplied);
            return;
        }

        allVideoDataForSort = [];
        $initialVideosOnPage.each(function(index) {
            const videoEntry = extractVideoData($(this));
            videoEntry.originalIndex = index;
            allVideoDataForSort.push(videoEntry);
        });
        initialVideoDataCache = [...allVideoDataForSort];
        console.log(`Initial videos processed: ${allVideoDataForSort.length}`);

        let $pageLinks = $('a[href*="page="], .page, .page-item').filter(function() {
            return $(this).text().match(/^\d+$/) || $(this).attr('href')?.match(/page=(\d+)/);
        });
        let totalSitePages = 1;
        if ($pageLinks.length) {
            let maxPage = 0;
            $pageLinks.each(function() {
                let pageNumText = $(this).text().trim();
                let pageNumHref = $(this).attr('href');
                let pageNum = parseInt(pageNumText, 10);
                if (isNaN(pageNum) && pageNumHref) {
                    const match = pageNumHref.match(/page=(\d+)/);
                    if (match && match[1]) pageNum = parseInt(match[1], 10);
                }
                if (!isNaN(pageNum) && pageNum > maxPage) maxPage = pageNum;
            });
            totalSitePages = Math.max(1, maxPage);
        }
        console.log('Total site pages detected:', totalSitePages);

        function completeGalleryFetchAndProceed() {
            console.log('All gallery pages fetched. Total videos:', allVideoDataForSort.length);
            if (currentSortKeyBeingApplied.startsWith('deep:')) {
                initiateDeepSortProcess(currentSortKeyBeingApplied);
            } else {
                currentLoadingIndicator.text('Sorting...');
                applySorting(allVideoDataForSort, currentSortKeyBeingApplied);
                currentLoadingIndicator.remove(); currentLoadingIndicator = null;
                displayPage(currentContainerForDisplay, allVideoDataForSort, 1, VIDEOS_PER_PAGE_DISPLAY);
            }
        }

        if (totalSitePages <= 1) {
            completeGalleryFetchAndProceed();
            return;
        }

        let baseUrl = window.location.href.split('?')[0].split('#')[0];
        let pagesFetchedSuccessfully = 1;

        function fetchPage(page) {
            if (page > totalSitePages) {
                completeGalleryFetchAndProceed();
                return;
            }
            currentLoadingIndicator.text(`Loading gallery page ${page} of ${totalSitePages}...`);
            $.get(`${baseUrl}?page=${page}`, function(data) {
                let $pageContent = $(data);
                let $videosOnFetchedPage = $pageContent.find(VIDEO_ITEM_SELECTOR);
                $videosOnFetchedPage.each(function() {
                     const videoEntry = extractVideoData($(this));
                     allVideoDataForSort.push(videoEntry);
                });
                pagesFetchedSuccessfully++;
                fetchPage(page + 1);
            }).fail(function() {
                console.log('Failed to fetch gallery page:', page, ". Skipping.");
                fetchPage(page + 1);
            });
        }
        fetchPage(2);
    }


    function waitForVideos(callback) {
        let attempts = 0;
        const maxAttempts = 20;
        const interval = setInterval(function() {
            let $videos = $(VIDEO_ITEM_SELECTOR);
            if ($videos.length > 0) {
                clearInterval(interval);
                callback($videos);
            } else {
                attempts++;
                if (attempts >= maxAttempts) {
                    clearInterval(interval);
                    console.log('Max attempts reached. No videos found.');
                    callback($([]));
                }
            }
        }, 500);
    }

    // --- New UI Panel Logic ---
    let panelSelectedSortId = DEFAULT_SORT_ID;
    let panelSelectedSortDirection = DEFAULT_SORT_DIR; // 'asc', 'desc', or null for 'random'

    function updateSortPanelUI() {
        // Update dropdown selection
        $('#mvs-primary-sort-select').val(panelSelectedSortId);

        // Update direction buttons
        const $dirControls = $('#mvs-sort-direction-controls-global');
        $dirControls.find('.mvs-dir-btn').removeClass('mvs-active-dir');

        const selectedSortDef = SORT_DEFINITIONS.find(s => s.id === panelSelectedSortId);
        if (selectedSortDef && !selectedSortDef.noDirection) {
            $dirControls.show();
            if (panelSelectedSortDirection === 'asc') {
                $dirControls.find('.mvs-dir-asc').addClass('mvs-active-dir');
            } else if (panelSelectedSortDirection === 'desc') {
                $dirControls.find('.mvs-dir-desc').addClass('mvs-active-dir');
            }
        } else {
            $dirControls.hide(); // Hide direction buttons for random sort
        }

        // Update active state of sort items (if they were still individual items)
        // In this new design, individual sort items are not clickable for selection,
        // so this part is no longer directly relevant for selection highlighting.
        // However, if we want to visually indicate the selected item in the list,
        // we would need to iterate through them and apply a class.
        // For now, the dropdown is the primary selection indicator.
    }


    $(document).ready(function() {
        addGlobalStyle(newStyles);

        waitForVideos(function($initialVideos) {
            let $videoGalleryContainer = $initialVideos.first().parent();
             if ($initialVideos.parent('.thumbs, .media-list, .video-list, #discover-grid-wrapper div.row, .thumbs.uploads').length > 0) {
                 $videoGalleryContainer = $initialVideos.parent('.thumbs, .media-list, .video-list, #discover-grid-wrapper div.row, .thumbs.uploads').first();
            } else if ($initialVideos.closest('.thumbs, .media-list, .video-list, #discover-grid-wrapper div.row, .thumbs.uploads').length > 0) {
                 $videoGalleryContainer = $initialVideos.closest('.thumbs, .media-list, .video-list, #discover-grid-wrapper div.row, .thumbs.uploads').first();
            }

            if ($initialVideos.length === 0 || $videoGalleryContainer.length === 0) {
                console.log('No video gallery detected. Sorter UI not added.');
                return;
            }

            // --- Create Main Toggle Button ---
            const $sortToggleButton = $(`
                <button id="mvs-sort-toggle-btn" title="Sort Options">
                    ${SVG_ICONS.filter}
                </button>
            `);
            $('body').append($sortToggleButton);

            // --- Create Sort Panel (initially hidden) ---
            const $sortPanelContainer = $(`
                <div id="mvs-sort-panel-container">
                    <div class="mvs-panel-header">
                        <div class="mvs-panel-header-top">
                            <h3>Sort Videos</h3>
                            <button class="mvs-panel-close-btn" title="Close">${SVG_ICONS.close}</button>
                        </div>
                        <select id="mvs-primary-sort-select"></select>
                        <div id="mvs-sort-direction-controls-global" class="mvs-sort-direction-controls">
                            <button class="mvs-dir-btn mvs-dir-asc" title="Sort Ascending" data-dir="asc">${SVG_ICONS.arrowUp}</button>
                            <button class="mvs-dir-btn mvs-dir-desc" title="Sort Descending" data-dir="desc">${SVG_ICONS.arrowDown}</button>
                        </div>
                    </div>
                    <div class="mvs-panel-body"></div>
                    <div class="mvs-panel-footer">
                        <button class="mvs-panel-action-btn mvs-clear-btn">Clear Sort</button>
                        <button class="mvs-panel-action-btn mvs-apply-btn">Apply Sort</button>
                    </div>
                </div>
            `);
            const $panelBody = $sortPanelContainer.find('.mvs-panel-body');
            const $primarySortSelect = $sortPanelContainer.find('#mvs-primary-sort-select');

            // Populate primary sort dropdown
            const iconMap = {
                title: '📝', views: '👁️', duration: '⏱️', uploader: '👤',
                uploadDate: '📅', favorites: '⭐', comments: '💬', resolution: '📺',
                engagementComments: '📈', engagementFavorites: '💖', random: '🎲'
            };

            const groupedSortsForDropdown = SORT_DEFINITIONS.reduce((acc, sortDef) => {
                if (!sortDef.noDirection) { // Only add sortable items to dropdown
                    acc[sortDef.group] = acc[sortDef.group] || [];
                    acc[sortDef.group].push(sortDef);
                } else if (sortDef.id === 'random') { // Add random as a special case
                    acc[sortDef.group] = acc[sortDef.group] || [];
                    acc[sortDef.group].push(sortDef);
                }
                return acc;
            }, {});

            for (const groupName in groupedSortsForDropdown) {
                const $optgroup = $(`<optgroup label="${groupName}"></optgroup>`);
                groupedSortsForDropdown[groupName].forEach(sortDef => {
                    const icon = iconMap[sortDef.id] || '▫️'; // Default icon if not found
                    $optgroup.append(`<option value="${sortDef.id}">${icon} ${sortDef.label}</option>`);
                });
                $primarySortSelect.append($optgroup);
            }
            
            // Set initial selection for dropdown
            $primarySortSelect.val(DEFAULT_SORT_ID);

            // The detailed list of sort types in the panel body is removed as per user feedback.
            // $panelBody will remain empty or can be hidden via CSS.

            $('body').append($sortPanelContainer);

            // --- Event Handlers ---
            $sortToggleButton.on('click', () => {
                $sortPanelContainer.fadeToggle(200);
                $sortToggleButton.toggle(); // Hide toggle button when panel opens
                updateSortPanelUI(); // Update UI when panel opens
            });
            
            const hidePanel = () => {
                $sortPanelContainer.fadeOut(200, () => {
                    $sortToggleButton.show(); // Show toggle button when panel closes
                });
            };

            $sortPanelContainer.find('.mvs-panel-close-btn').on('click', hidePanel);

            $primarySortSelect.on('change', function() {
                panelSelectedSortId = $(this).val();
                const selectedSortDef = SORT_DEFINITIONS.find(s => s.id === panelSelectedSortId);
                if (selectedSortDef && !selectedSortDef.noDirection) {
                    panelSelectedSortDirection = selectedSortDef.defaultDir;
                } else {
                    panelSelectedSortDirection = null; // For random or non-directional sorts
                }
                updateSortPanelUI();
            });

            $('#mvs-sort-direction-controls-global .mvs-dir-btn').on('click', function() {
                panelSelectedSortDirection = $(this).data('dir');
                updateSortPanelUI();
            });

            $sortPanelContainer.find('.mvs-apply-btn').on('click', function() {
                const activeSortDef = SORT_DEFINITIONS.find(s => s.id === panelSelectedSortId);
                if (!activeSortDef) return;

                let sortKeyToApply;
                if (activeSortDef.noDirection) {
                    sortKeyToApply = activeSortDef.key;
                } else {
                    sortKeyToApply = activeSortDef.keys[panelSelectedSortDirection];
                }

                if (sortKeyToApply) {
                    prepareAndSortGallery($videoGalleryContainer, $(VIDEO_ITEM_SELECTOR), sortKeyToApply);
                }
                hidePanel(); // Hide panel after applying sort
            });

            $sortPanelContainer.find('.mvs-clear-btn').on('click', function() {
                panelSelectedSortId = DEFAULT_SORT_ID; // Revert to default
                panelSelectedSortDirection = DEFAULT_SORT_DIR;
                updateSortPanelUI();
                // For now, just resets panel. User must click "Apply Sort".
            });

            // Initialize panel UI
            updateSortPanelUI();
            console.log("Advanced Sorter Panel UI added.");
        });
    });
})(jQuery);