Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/* Helper for the "10+ edits in past 30 days" criterion for the Wikipedia Library.
    Displays how long you can go without editing until access expires,
    and advice on when to edit. */
$(document).ready(function () {
    const thresholdN = 10; // Number of required edits
    const windowDays = 30; // Within this many days
    const hoardRateDays = 3; // Ideal rate: 1 edit every X days

    const millisecondsInDay = 24 * 60 * 60 * 1000;
    const fallOffAmount = windowDays * millisecondsInDay; // 30 days in milliseconds

    const dateFormatter = new Intl.DateTimeFormat(undefined, {
        weekday: 'short',
        day: '2-digit',
        month: 'long',
        year: 'numeric'
    });
    const timeFormatter = new Intl.DateTimeFormat(undefined, {
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        hour12: true, // For AM/PM
        timeZoneName: 'short'
    });

    /**
     * Formats a timestamp into a div element with date and time parts.
     * @param {string} prefixText - Text to prefix the timestamp display.
     * @param {number} timestampMs - The timestamp in milliseconds.
     * @returns {HTMLDivElement} - A div element containing the formatted timestamp.
     */
    function formatTimestampToElements(prefixText, timestampMs, strong = false) {
        const date = new Date(timestampMs);
        const datePart = dateFormatter.format(date);
        const timePart = timeFormatter.format(date);
        const timestampContainerDiv = document.createElement('div');
        timestampContainerDiv.className = 'twl-timestamp-display'; // Add a class for potential styling
        let prefix = document.createTextNode(`${prefixText}:`);
        if (strong) {
            let prefixS = document.createElement('strong');
            prefixS.appendChild(prefix);
            timestampContainerDiv.appendChild(prefixS);
        } else {
            timestampContainerDiv.appendChild(prefix);
        }
        timestampContainerDiv.appendChild(document.createElement('br'));
        timestampContainerDiv.appendChild(document.createTextNode(datePart));
        timestampContainerDiv.appendChild(document.createElement('br'));
        timestampContainerDiv.appendChild(document.createTextNode(timePart));
        return timestampContainerDiv;
    }

    /**
     * Calculates the Earliest Start Time for making an edit to maintain the 1-edit/3-days cadence.
     * This is the earliest time that an edit will advance the user's access by 3 full days.
     * @param {number[]} sortedTimestamps - Array of edit timestamps, sorted newest to oldest.
     * @returns {number} The earliest optimal time to make an edit in milliseconds.
     */
    function calculateEarliestStartTime(sortedTimestamps) {
        console.assert(sortedTimestamps.length == thresholdN, "must have exactly thresholdN timestamps");
        let earliestStartTime = Infinity; // Initialize to infinity to find the min time

        // We only care about the 9 newest edits (indices 0 to 8) for maintaining the cadence.
        // The 10th edit (index 9) is only relevant for the hard expiration.
        for (let i = 0; i < thresholdN - 1; i++) { // Loop through up to the 9th newest edit
            const currentEditTimestamp = sortedTimestamps[i];
            // The deadline for this edit
            let potentialExpiry = currentEditTimestamp + fallOffAmount; // 30 days after this edit
            let numEditsToMake = thresholdN - (i + 1); // How many more edits are needed to reach 10
            let requiredHoardEditTime = potentialExpiry - (numEditsToMake * hoardRateDays * millisecondsInDay);

            earliestStartTime = Math.min(earliestStartTime, requiredHoardEditTime);
        }
        return earliestStartTime; // + 3 days for new edits shifting - 3 days of offset of deadline of current = just the earliest start time
    }

    function addPortlet(element) {
        /* todo: complicated skin logic */
        let portlets = ['p-personal', 'p-personal-sticky-header'];
        for (let portletId of portlets) {
            const portletListItem = mw.util.addPortletLink(portletId, "", '', 'pt-librarylimit', '', null, '#pt-logout');
            if (portletListItem) {
                portletListItem.firstChild.remove();
                portletListItem.append(element.cloneNode(true));
            }
        }
    }


    // Ensure that mediawiki.user and mediawiki.api modules are loaded before proceeding.
    mw.loader.using(['mediawiki.user', 'mediawiki.api'], function() {
        // Get the current username from MediaWiki configuration.
        const username = mw.config.get('wgUserName');

        // Check if a username is available. If not, there's no user to query.
        if (!username) {
            console.log('No user logged in or username not available.');
            return;
        }

        // Initialize a new MediaWiki API object.
        (new mw.Api()).get({
            action: 'query',
            list: 'usercontribs',
            ucuser: username,
            uclimit: thresholdN, // Request exactly thresholdN (10) contributions
            ucprop: 'timestamp'
        }).done(function(result) {
            if (result.query && result.query.usercontribs && result.query.usercontribs.length >= thresholdN) {
                const userContribs = result.query.usercontribs;
                let timestamps = userContribs.map(contrib => new Date(contrib.timestamp).getTime());
                window.timestamps = timestamps; // Store timestamps globally for debugging

                const now = new Date().getTime();// Current time in milliseconds

                let displayContent = document.createElement('div');
                displayContent.className = 'twl-widget-content';

                // --- Core Logic for Edit Window ---

                // Calculate Expiration Time (ET) - this is the hard deadline
                const expirationTime = timestamps[thresholdN - 1] + fallOffAmount; // 10th newest edit + 30 days

                // Calculate Earliest Start Time - based on maintaining cadence
                const optimalStartTime = calculateEarliestStartTime(timestamps);

                // --- Display Logic ---

                if (now < optimalStartTime) {
                    const editWindowStartDiv = formatTimestampToElements('Wait', optimalStartTime, false);
                    editWindowStartDiv.style.border = '1px solid black';
                    displayContent.appendChild(editWindowStartDiv);
                } else {
                    let editsNeededNow = Math.min(thresholdN, Math.ceil((now - optimalStartTime) / (hoardRateDays * millisecondsInDay)));
                    const editWindowStartDiv = formatTimestampToElements(`Do ${editsNeededNow} edits`, optimalStartTime, true);
                    editWindowStartDiv.style.border = '1px solid black';
                    editWindowStartDiv.classList.add((now >= expirationTime) ? 'mw-plusminus-neg' : 'mw-plusminus-pos');
                    displayContent.appendChild(editWindowStartDiv);
                }

                const expirationDiv = formatTimestampToElements('Expire', expirationTime, now >= expirationTime);
                expirationDiv.style.border = '1px solid black';
                expirationDiv.style.borderTop = 'none';
                displayContent.appendChild(expirationDiv);
                addPortlet(displayContent);
            } else {
                console.log('Error: No user contributions found or query failed.');
                displayContent = document.createElement('div');
                displayContent.innerHTML = "<p>Could not retrieve edit history. Please check the console.</p>";
                addPortlet(displayContent);
            }
        }).fail(function(jqXHR, textStatus, errorThrown) {
            console.error('MediaWiki API request failed:', textStatus, errorThrown);
            const displayContent = document.createElement('div');
            displayContent.innerHTML = `<p>Error fetching edit history: ${textStatus}.</p>`;
            addPortlet(displayContent);
        });
    });
});