// ==UserScript==
// @name         MangaUpdates Hover Preview
// @namespace    https://github.com/Reibies
// @version      1.21
// @description  Show cover image on hover over MangaUpdates series link
// @author       Reibies
// @match        https://www.mangaupdates.com/*
// @icon         https://www.mangaupdates.com/favicon.ico
// @grant        GM_xmlhttpRequest
// @connect      api.mangaupdates.com
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function() {
    'use strict';

    const config = {
        COOLDOWN_TIME: 1000,
        RETRY_LIMIT: 3,
        RETRY_DELAY: 3000,
        CACHE_LIMIT: 1000,
        BASE_URL: 'https://api.mangaupdates.com/v1/series/',
        PLACEHOLDER_IMAGE_URL: "https://placeholder.pics/svg/150x230/d0d8e2/52667c/[No%20Cover]",
    };

    let cache = JSON.parse(localStorage.getItem('mangaUpdatesCoverCache')) || {};
    const cooldown = {};
    let currentPreviewLink = null;

    const updateCache = () => {
        const keys = Object.keys(cache);
        if (keys.length > config.CACHE_LIMIT) {
            delete cache[keys[0]];
        }
        localStorage.setItem('mangaUpdatesCoverCache', JSON.stringify(cache));
    };

    const fetchCoverImage = (seriesId, callback, retryCount = 0) => {
        if (cache[seriesId] && (Date.now() - cache[seriesId].timestamp < config.COOLDOWN_TIME)) {
            return callback(null, cache[seriesId].url);
        }

        if (cooldown[seriesId]) {
            const remainingTime = Math.ceil((cooldown[seriesId] - Date.now()) / 1000);
            return callback(`Cooling down for ${remainingTime} seconds.`);
        }

        cooldown[seriesId] = Date.now() + config.COOLDOWN_TIME;
        setTimeout(() => delete cooldown[seriesId], config.COOLDOWN_TIME);

        GM_xmlhttpRequest({
            method: "GET",
            url: `${config.BASE_URL}${seriesId}`,
            onload: (response) => {
                if (response.status === 200) {
                    try {
                        const { image } = JSON.parse(response.responseText);
                        const imageUrl = image?.url?.original || config.PLACEHOLDER_IMAGE_URL;
                        cache[seriesId] = { url: imageUrl, timestamp: Date.now() };
                        updateCache();
                        return callback(null, imageUrl);
                    } catch (e) {
                        return callback("Error parsing response");
                    }
                } else if (response.status === 404) {
                    cache[seriesId] = { url: config.PLACEHOLDER_IMAGE_URL, timestamp: Date.now() };
                    updateCache();
                    return callback("Image not found");
                } else if (response.status >= 500 && retryCount < config.RETRY_LIMIT) {
                    setTimeout(() => fetchCoverImage(seriesId, callback, retryCount + 1), config.RETRY_DELAY);
                } else {
                    return callback(`Error: ${response.status}`);
                }
            },
            onerror: () => callback("Unable to fetch image"),
        });
    };

    const createPreviewElement = () => {
        let preview = $('#preview-image');
        if (preview.length === 0) {
            preview = $('<div id="preview-image"></div>').css({
                position: 'absolute',
                zIndex: 1000,
                border: '1px solid #ccc',
                background: '#fff',
                padding: '5px',
                maxWidth: '200px',
                maxHeight: '300px',
                display: 'none',
                boxShadow: '0 0 10px rgba(0, 0, 0, 0.5)',
                borderRadius: '5px',
            });
            $('body').append(preview);
        }
        return preview;
    };

    const debounce = (func, wait) => {
        let timeout;
        return (...args) => {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    };

    const cleanupPreview = () => {
        const preview = $('#preview-image');
        if (preview.length) {
            preview.hide();
            if (currentPreviewLink) {
                $(currentPreviewLink).off('mousemove');
                clearInterval($(currentPreviewLink).data('countdownInterval'));
                currentPreviewLink = null;
            }
        }
    };

    const showPreviewImage = function(event) {
        const linkElement = $(this);
        const url = linkElement.attr('href');

        if (url.includes('?act=')) return;

        currentPreviewLink = this;
        const seriesId = extractSeriesIdFromUrl(url);

        if (!seriesId) return;

        const preview = createPreviewElement();

        const updatePreview = (content) => {
            preview.html(content).css({
                top: `${event.pageY + 10}px`,
                left: `${event.pageX + 10}px`,
                display: 'block'
            });
        };

      const refreshCountdown = () => {
    const remainingTime = Math.ceil((cooldown[seriesId] - Date.now()) / 1000);
    if (remainingTime > 0) {
        updatePreview(`<div style="width: 200px; height: 300px; display: flex; justify-content: center; align-items: center;"><span>⚠️ Cooling down for ${remainingTime} seconds</span></div>`);
    } else {
        fetchCoverImage(seriesId, (err, imageUrl) => {
            if (err || !imageUrl) {
                updatePreview(`<div style="width: 200px; height: 300px; display: flex; justify-content: center; align-items: center;"><span>⚠️ ${err}</span></div>`);
            } else {
                updatePreview(`<img src="${imageUrl}" alt="Cover Image" style="max-width: 100%; max-height: 100%;">`);
            }
        });
        clearInterval(linkElement.data('countdownInterval'));
    }
};

        if (linkElement.data('countdownInterval')) {
            clearInterval(linkElement.data('countdownInterval'));
        }

linkElement.data('countdownInterval', setInterval(refreshCountdown, 1000));
refreshCountdown();

        $(this).on('mousemove', debounce((e) => {
            preview.css({
                top: `${e.pageY + 10}px`,
                left: `${e.pageX + 10}px`
            });
        }, 10));
    };

    const hidePreviewImage = function() {
        cleanupPreview();
    };

    const originalPushState = history.pushState;
    const originalReplaceState = history.replaceState;

    history.pushState = function(...args) {
        originalPushState.apply(this, args);
        cleanupPreview();
    };

    history.replaceState = function(...args) {
        originalReplaceState.apply(this, args);
        cleanupPreview();
    };

    window.addEventListener('popstate', cleanupPreview);

    $(document).ready(() => {
        $(document).on('click', cleanupPreview);

        $('body').on('mouseenter', 'a[href^="https://www.mangaupdates.com/series/"]', function(event) {
            showPreviewImage.call(this, event);
        });

        $('body').on('mouseleave', 'a[href^="https://www.mangaupdates.com/series/"]', hidePreviewImage);
    });

    const extractSeriesIdFromUrl = (url) => {
        const base36EncodedId = url.split('/')[4];
        return parseInt(base36EncodedId, 36);
    };
})();