// ==UserScript==
// @name         F95 – add “Compressed version” link
// @namespace    https://example.com/
// @version      1.7.2
// @description  Finds the newest, most relevant compressed build in the same thread and links it above the download section.
// @author       You
// @match        https://f95zone.to/*threads/*
// @grant        GM_xmlhttpRequest
// @connect      f95zone.to
// ==/UserScript==

/* -------------------------------------------------- */
/* ------------------  helpers  --------------------- */
/* -------------------------------------------------- */

/**
 * ROBUST PARSER (V6)
 * - Dynamically handles Chapter/Episode.
 * - Correctly parses version strings like "v.36072" and "v0.4".
 */
function parseVersionKey(text) {
    if (!text) return null;

    const cleanedText = text.replace(/[\[\]\(\)]/g, ' ');

    // Match Season + Chapter/Episode
    const seasonRegex = /s(?:eason)?\s*(\d+).*?(ch|ep|chapter|episode)\.?\s*(\d+)/i;
    let match = cleanedText.match(seasonRegex);
    if (match && match[1] && match[2] && match[3]) {
        const season = parseInt(match[1], 10);
        const typeStr = match[2].toLowerCase();
        const number = parseInt(match[3], 10);
        const displayType = typeStr.startsWith('ch') ? 'Ch.' : 'Ep.';
        return { key: season + number / 100, valueStr: `S${season} ${displayType} ${number}` };
    }

    // Match standalone Chapter/Episode
    const standaloneRegex = /\b(ch|ep|chapter|episode)\.?\s*(\d+)/i;
    match = cleanedText.match(standaloneRegex);
    if (match && match[1] && match[2]) {
        const typeStr = match[1].toLowerCase();
        const number = parseInt(match[2], 10);
        const displayType = typeStr.startsWith('ch') ? 'Ch.' : 'Ep.';
        return { key: 1 + number / 100, valueStr: `${displayType} ${number}` };
    }

    // Flexible regex for standard versions like "v.123", "v 123", "v-123", and "v123"
    const versionRegex = /v(?:ersion)?[\s._-]?(\d[\d.]*)/i;
    match = cleanedText.match(versionRegex);
    if (match && match[1]) {
        return { key: parseFloat(match[1]), valueStr: `v${match[1]}` };
    }

    return null;
}


function createCompressedVersionLink(label, href) {
    const link = document.createElement('a');
    link.href = href;
    link.textContent = label;
    link.target = '_blank';
    link.rel = "noopener noreferrer";
    link.style.cssText = `
        display: inline-block; margin: 10px 0 12px 0; padding: 8px 14px;
        background-color: #ba4545; color: white; font-weight: bold;
        font-size: 14px; text-decoration: none; border-radius: 4px; cursor: pointer;
    `;
    link.title = 'Go to compressed version post';
    return link;
}

function insertLinkAboveDownloadDiv(firstPost, element) {
    const candidates = firstPost.querySelectorAll('div[style*="text-align: center"]');
    for (const div of candidates) {
        if (div.textContent.trim().toUpperCase().includes('DOWNLOAD')) {
            div.parentNode.insertBefore(element, div);
            return true;
        }
    }
    const firstSpoiler = firstPost.querySelector('.bbCodeSpoiler');
    if (firstSpoiler) {
        firstSpoiler.parentNode.insertBefore(element, firstSpoiler);
    } else {
        firstPost.insertBefore(element, firstPost.firstChild);
    }
    return false;
}

function isGameReleasePost(post) {
    if (!post) return false;
    const centerDivs = post.querySelectorAll('div[style*="text-align: center"]');
    for (const div of centerDivs) {
        if (div.textContent.trim().toUpperCase().includes('DOWNLOAD')) return true;
    }
    const spoilerButtons = post.querySelectorAll('.bbCodeSpoiler-button');
    for (const button of spoilerButtons) {
        const buttonText = button.textContent.trim().toUpperCase();
        if (buttonText.includes('DOWNLOAD') || buttonText.includes('LINKS')) return true;
    }
    return false;
}

function looksLikeQuote(snippet) {
    if (!snippet) return false;
    const s = snippet.trim().toLowerCase();
    return s.includes('said:') || s.startsWith('quote') || s.startsWith('spoiler');
}

function isIgnorableComment(text) {
    if (!text) return false;
    const s = text.trim().toLowerCase();
    const requestKeywords = [
        'request', 'looking for', 'anyone has', 'does anyone have',
        'can someone', 'anyone have', 'does anyone know', 'need', 'searching for',
        'is this', 'is there', 'or what'
    ];
    // Check for keywords OR if the snippet ends in a question mark
    return requestKeywords.some(keyword => s.includes(keyword)) || s.endsWith('?');
}

/* -------------------------------------------------- */
/* --------  CORE: search-result processing  -------- */
/* -------------------------------------------------- */

function findBestMatchingCompressedPost(html) {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    const items = [...doc.querySelectorAll('li.block-row, li.js-inlineModContainer')];

    if (!items.length) {
        console.warn('findBestMatchingCompressedPost(): no <li> items found in search results.');
        return null;
    }
    console.log("DEBUG: Found search result items =", items.length);

    const compressedRegex = /(compressed|compress|archive|\.zip|\.rar|\.7z)/i;
    const releaseIndicatorRegex = /(mega|pixeldrain|workupload|buzzheavier|datanodes|akirabox|win|linux|mac|gb|mb)/i;
    const hasLinkRegex = /\[(URL|CODE|SPOILER)/i;

    const candidates = items.map((li, idx) => {
        const contentRow = li.querySelector('.contentRow-main, .contentRow');
        if (!contentRow) return null;

        let postLinkElem = li.querySelector('.contentRow-minor a[href*="/post-"]');
        if (!postLinkElem) {
            postLinkElem = contentRow.querySelector('h3.contentRow-title a[href]');
        }
        if (!postLinkElem) return null;
        const postUrl = postLinkElem.href;

        const snippetElem = contentRow.querySelector('.contentRow-snippet');
        const snippetText = snippetElem ? snippetElem.innerText.trim() : '';

        // --- FILTERING LOGIC ---
        // 1. Basic checks: Must not be a quote/comment and must mention compression.
        if (looksLikeQuote(snippetText) || isIgnorableComment(snippetText) ||
            !compressedRegex.test(snippetText) || !releaseIndicatorRegex.test(snippetText)) {
            return null;
        }

        // 2. Strong signal check: Must have EITHER a parsable version OR a BBCode link tag.
        //    This prevents matching simple comments while allowing valid posts where the
        //    link tag is outside the truncated snippet.
        const candidateVersionInfo = parseVersionKey(snippetText);
        const hasLinkTag = hasLinkRegex.test(snippetText);

        if (!candidateVersionInfo && !hasLinkTag) {
            return null;
        }

        // 3. Final data check
        const timeElem = li.querySelector('time.u-dt');
        if (!timeElem || !timeElem.getAttribute('datetime')) return null;
        const dateObj = new Date(timeElem.getAttribute('datetime'));
        if (isNaN(dateObj.getTime())) return null;

        const versionStr = candidateVersionInfo ? candidateVersionInfo.valueStr : "N/A";
        console.log(`%cDEBUG [${idx}] Candidate FOUND: URL=${postUrl}, Version=${versionStr}`, 'color: lightgreen; font-weight: bold;');
        return { url: postUrl, date: dateObj, versionInfo: candidateVersionInfo };
    }).filter(Boolean);

    if (!candidates.length) {
        console.log("DEBUG: No valid candidates found after filtering.");
        return null;
    }

    // Sort by newest date first to get the most recent release.
    candidates.sort((a, b) => b.date - a.date);
    return candidates[0];
}


/* -------------------------------------------------- */
/* -----------  helper: POST form search  ----------- */
/* -------------------------------------------------- */

function postSearchUsingPageForm(searchQuery) {
    return new Promise((resolve, reject) => {
        const forms = document.querySelectorAll('form[action="/search/search"]');
        let searchForm = null;
        for (const form of forms) {
            if (form.querySelector('select[name="constraints"]')) {
                searchForm = form;
                break;
            }
        }
        if (!searchForm) {
            return reject(new Error("Could not find a search form with a 'constraints' dropdown. The page structure may have changed."));
        }
        const formData = new FormData(searchForm);
        formData.set('keywords', searchQuery);
        const searchUrl = searchForm.action;
        GM_xmlhttpRequest({
            method: "POST", url: searchUrl, data: formData,
            headers: { "User-Agent": navigator.userAgent, "Referer": window.location.href },
            onload: r => {
                if (r.status >= 200 && r.status < 300) resolve(r.responseText);
                else reject(new Error(`HTTP ${r.status}: ${r.statusText}`));
            },
            onerror: err => reject(new Error('Network error during GM_xmlhttpRequest')),
        });
    });
}

/* -------------------------------------------------- */
/* -------------------  main  ----------------------- */
/* -------------------------------------------------- */

window.addEventListener('load', () => {
    (async function main() {
        try {
            const firstPost = document.querySelector('.message-threadStarterPost.message--post.js-post');

            if (!firstPost || !isGameReleasePost(firstPost) || /compressed/i.test(firstPost.textContent)) {
                return;
            }

            const searchQuery = 'compressed';
            const html = await postSearchUsingPageForm(searchQuery);

            const bestCandidate = findBestMatchingCompressedPost(html);

            if (!bestCandidate) {
                const warn = document.createElement('span');
                warn.textContent = 'No matching compressed version found.';
                warn.style.cssText = 'color:#ba4545; display:block; margin:10px 0 12px;';
                insertLinkAboveDownloadDiv(firstPost, warn);
                return;
            }

            const versionStr = bestCandidate.versionInfo ? bestCandidate.versionInfo.valueStr : 'Link';
            const linkLabel = `Compressed Version (${versionStr})`;

            const link = createCompressedVersionLink(linkLabel, bestCandidate.url);
            insertLinkAboveDownloadDiv(firstPost, link);

        } catch (e) {
            console.error("F95 Compressed Link Script Error:", e);
        }
    })();
});
-------------------------------------
why script doesn't works at pages https://f95zone.to/threads/ravager-v5-2-5-4minutewarning.26476/ and https://f95zone.to/threads/the-king-of-summer-v0-5-17-early-no-try-studios.139047/