// ==UserScript==
// @name         JYNGMN Image Downloader (Aggressive Retry + Perfect Order)
// @namespace    http://tampermonkey.net/
// @version      1.43
// @description  Ultra-fast downloads with aggressive retries and perfect ordering
// @author       ChatGPT
// @match        https://www.xinggan5.top/*
// @connect      *
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// ==/UserScript==

(function () {
    'use strict';

    const PAGE_CONCURRENCY = 50;
    const IMAGE_CONCURRENCY = 50;
    const MAX_IMMEDIATE_RETRIES = 5;
    const RETRY_DELAY = 100; // More aggressive 500ms initial delay
    const PAGE_TIMEOUT = 1000; // 10 second timeout per page
    const VALID_IMAGE_EXTS = ['jpg', 'jpeg', 'png', 'webp', 'bmp', 'tiff'];

    // Ultra-aggressive retry with immediate attempts
    async function aggressiveFetch(url, options = {}, retries = MAX_IMMEDIATE_RETRIES) {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), PAGE_TIMEOUT);

        try {
            const response = await fetch(url, {
                ...options,
                signal: controller.signal
            });
            clearTimeout(timeoutId);
            if (!response.ok) throw new Error(`HTTP ${response.status}`);
            return response;
        } catch (error) {
            clearTimeout(timeoutId);
            if (retries > 0) {
                console.log(`Immediate retry ${MAX_IMMEDIATE_RETRIES - retries + 1} for ${url}`);
                await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
                return aggressiveFetch(url, options, retries - 1);
            }
            throw error;
        }
    }

    function makeAbsoluteURL(url, base) {
        try { return new URL(url, base || window.location.origin).href; }
        catch { return url; }
    }

    async function injectScript(url) {
        return new Promise((resolve, reject) => {
            if (document.querySelector(`script[src="${url}"]`)) { resolve(); return; }
            const script = document.createElement('script');
            script.src = url;
            script.onload = () => resolve();
            script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
            document.head.appendChild(script);
        });
    }

    async function fetchImage(url) {
        return aggressiveFetch(url, { mode: 'cors', credentials: 'omit' });
    }

    function getFileExtension(url) {
        if (!url || typeof url !== 'string') return 'jpg';
        try {
            const cleanUrl = url.split('?')[0].split('#')[0];
            const m = cleanUrl.match(/\.([a-zA-Z0-9]+)$/);
            const ext = m ? m[1].toLowerCase() : 'jpg';
            return VALID_IMAGE_EXTS.includes(ext) ? ext : 'jpg';
        } catch {
            return 'jpg';
        }
    }

    async function createAndDownloadZip(images, zipName) {
        try {
            const zip = new JSZip();

            // Sort by original position
            images.sort((a, b) => (a.pageNumber - b.pageNumber) || (a.positionOnPage - b.positionOnPage));

            let counter = 1;
            for (const img of images) {
                if (!img.blob) continue;
                const ext = getFileExtension(img.url);
                const filename = `${String(counter++).padStart(3, '0')}.${ext}`;
                zip.file(filename, img.blob);
            }

            const blob = await zip.generateAsync({ type: 'blob', compression: 'STORE' });
            const blobUrl = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = blobUrl;
            a.download = zipName;
            a.style.display = 'none';
            document.body.appendChild(a);
            a.click();
            setTimeout(() => URL.revokeObjectURL(blobUrl), 10000);
        } catch (e) {
            alert(`ZIP creation failed: ${e.message}`);
        }
    }

    async function getPageHTML(url) {
        try {
            const response = await aggressiveFetch(url);
            const text = await response.text();
            return $(text);
        } catch (e) {
            console.error(`Failed to load page after ${MAX_IMMEDIATE_RETRIES} retries: ${url}`);
            return null;
        }
    }

    function getPaginationUrls($doc, baseUrl) {
        const urls = [];
        const pagination = $doc.find('div.pagination');
        if (pagination.length) {
            pagination.find('a[href]').each(function () {
                const txt = $(this).text().trim();
                if (/^\d+$/.test(txt)) {
                    const href = $(this).attr('href');
                    if (href) urls.push(makeAbsoluteURL(href, baseUrl));
                }
            });
        }
        if (urls.length === 0) urls.push(baseUrl);
        return [...new Set(urls)].sort((a, b) => {
            const aNum = parseInt(a.match(/-(\d+)\.html/)?.[1] || 0);
            const bNum = parseInt(b.match(/-(\d+)\.html/)?.[1] || 0);
            return aNum - bNum;
        });
    }

    function extractImageUrlsFromDoc($doc, pageUrl, pageNumber) {
        const imgs = [];
        $doc.find('article.article-content p img[src]').each(function (index) {
            const src = $(this).attr('src');
            if (src && !src.toLowerCase().endsWith('.gif')) {
                imgs.push({
                    url: makeAbsoluteURL(src, pageUrl),
                    pageNumber: pageNumber,
                    positionOnPage: index + 1,
                    originalUrl: src
                });
            }
        });
        return imgs;
    }

    async function downloadImagesFromUrl(linkUrl, useCurrentPage = false) {
        try {
            const startTime = Date.now();
            const $baseDoc = useCurrentPage ? $(document) : await getPageHTML(linkUrl);
            if (!$baseDoc) throw new Error("Failed to load initial page");

            let title = 'images';
            const h1 = $baseDoc.find('h1.article-title').text();
            if (h1) title = h1.trim().replace(/[\\/:*?"<>|]/g, '_');

            const pageUrls = getPaginationUrls($baseDoc, linkUrl);
            console.log(`Found ${pageUrls.length} pages to scan`);

            // Ultra-fast parallel scanning with immediate retries
            const allImages = [];
            const pagePromises = [];

            for (let i = 0; i < pageUrls.length; i++) {
                const pageNumber = i + 1;
                const pageUrl = pageUrls[i];

                pagePromises.push((async () => {
                    // For first page, reuse already loaded document if possible
                    if (i === 0 && useCurrentPage) {
                        const pageImages = extractImageUrlsFromDoc($baseDoc, pageUrl, pageNumber);
                        allImages.push(...pageImages);
                        console.log(`Scanned page ${pageNumber}: ${pageImages.length} images`);
                        return;
                    }

                    // For other pages, fetch with aggressive retries
                    const $pageDoc = await getPageHTML(pageUrl);
                    if ($pageDoc) {
                        const pageImages = extractImageUrlsFromDoc($pageDoc, pageUrl, pageNumber);
                        allImages.push(...pageImages);
                        console.log(`Scanned page ${pageNumber}: ${pageImages.length} images`);
                    }
                })());

                // Control concurrency
                if (pagePromises.length >= PAGE_CONCURRENCY) {
                    await Promise.race(pagePromises);
                }
            }

            await Promise.all(pagePromises);

            // Remove duplicates while keeping first occurrence
            const uniqueImages = [];
            const seenUrls = new Set();
            for (const img of allImages) {
                if (!seenUrls.has(img.url)) {
                    seenUrls.add(img.url);
                    uniqueImages.push(img);
                }
            }

            if (uniqueImages.length === 0) {
                alert('No images found to download.');
                return;
            }

            console.log(`Total unique images: ${uniqueImages.length}`);

            // Blazing fast parallel downloads
            const downloadedImages = [];
            const downloadPromises = [];

            for (let i = 0; i < uniqueImages.length; i++) {
                const img = uniqueImages[i];

                downloadPromises.push((async () => {
                    try {
                        const response = await fetchImage(img.url);
                        const blob = await response.blob();
                        downloadedImages.push({
                            ...img,
                            blob: blob
                        });
                        console.log(`Downloaded P${img.pageNumber}.${img.positionOnPage} (${downloadedImages.length}/${uniqueImages.length})`);
                    } catch (err) {
                        console.error(`Failed P${img.pageNumber}.${img.positionOnPage}:`, err);
                    }
                })());

                // Control concurrency
                if (downloadPromises.length >= IMAGE_CONCURRENCY) {
                    await Promise.race(downloadPromises);
                }
            }

            await Promise.all(downloadPromises);

            if (downloadedImages.length === 0) {
                alert('No images downloaded successfully.');
                return;
            }

            await createAndDownloadZip(downloadedImages, `${title}.zip`);
            console.log(`Total time: ${((Date.now() - startTime)/1000).toFixed(1)}s`);

        } catch (e) {
            console.error('Download failed:', e);
            alert('Error: ' + e.message);
        }
    }

    // [Rest of the UI code remains identical...]
    function addDownloadButton(targetElement, linkUrl, useCurrentPage = false, insertMode = 'after') {
        if (insertMode === 'append') {
            if (targetElement.querySelector('button.download-button')) return;
        } else if (insertMode === 'after') {
            if (targetElement.nextElementSibling?.classList.contains('download-button')) return;
        }

        const button = document.createElement('button');
        button.innerText = 'DOWNLOAD';
        button.classList.add('download-button');

        Object.assign(button.style, {
            marginLeft: '10px',
            padding: '3px 8px',
            cursor: 'pointer',
            backgroundColor: '#FB1C76',
            color: '#fff',
            border: 'none',
            borderRadius: '4px',
            fontSize: '12px',
            transition: 'background-color 0.3s',
            boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
        });

        button.addEventListener('mouseenter', () => {
            button.style.backgroundColor = '#AA124F';
        });
        button.addEventListener('mouseleave', () => {
            button.style.backgroundColor = '#FB1C76';
        });

        button.addEventListener('click', async (event) => {
            event.preventDefault();
            button.disabled = true;
            const originalText = button.innerText;
            button.innerText = 'Processing...';

            await downloadImagesFromUrl(linkUrl, useCurrentPage);

            button.innerText = originalText;
            button.disabled = false;
        });

        if (insertMode === 'append') {
            targetElement.appendChild(button);
        } else {
            targetElement.insertAdjacentElement('afterend', button);
        }
    }

    function findAndAddButtons() {
        document.querySelectorAll('div.node p a b').forEach(el => {
            const aTag = el.parentElement;
            if (aTag && !aTag.nextElementSibling?.classList.contains('download-button')) {
                const href = aTag.getAttribute('href');
                if (href) {
                    addDownloadButton(el, makeAbsoluteURL(href, window.location.href), false, 'after');
                }
            }
        });

        document.querySelectorAll('div.toptip').forEach(div => {
            if (!div.querySelector('button.download-button')) {
                addDownloadButton(div, window.location.href, true, 'append');
            }
        });
    }

    async function initializeScript() {
        console.log('Initializing Image Downloader...');
        try {
            await injectScript('https://cdn.jsdelivr.net/npm/jszip@3.10.0/dist/jszip.min.js');
            if (typeof JSZip === 'undefined') throw new Error('JSZip failed to load.');
            findAndAddButtons();

            const observer = new MutationObserver(() => findAndAddButtons());
            observer.observe(document.body, { childList: true, subtree: true });
        } catch (e) {
            console.error('Init failed:', e);
            alert('Failed to initialize the downloader script.');
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
        initializeScript();
    }
})();

fix download button of div.toptip to have the same function as the rest of the download button.