/**
 * utils/utils.js
 *
 * @file Provides miscellaneous utility functions
 *
 * @author Jessie Hildebrandt
 */

/* -------------------------------------------------------------------------- */
/* Module functions */

/* ---------------------------------- */
/* cancelRequestsToURL */

/**
 * Cancels any scheduled or active requests to the provided URL.
 *
 * @param {string} url - The URL to which any scheduled or active requests will be canceled.
 */
function cancelRequestsToURL( url )
{

    if ( url in requestTimers && requestTimers[ url ] !== null )
    {
        clearTimeout( requestTimers[ url ] );
        requestTimers[ url ] = null;
    }

    if ( url in activeRequests && activeRequests[ url ] !== null )
    {
        activeRequests[ url ].abort();
        activeRequests[ url ] = null;
    }

}

/* ---------------------------------- */
/* delayedInterruptableRequest */

let requestTimers = {};

/**
 * Perform a delayed interruptable GET request
 * If a previous request to the provided URL is still active,
 * it will be aborted before a new one is scheduled.
 *
 * @param {string} url - The URL to make the request to
 * @param {string} params - A parameter string to append to the request URL
 * @param {number} milliseconds - How long to wait before attempting to make the request
 *
 * @returns {Promise} Promise that will contain a response to the request
 */
async function delayedInterruptableRequest( url, params, milliseconds )
{

    if ( url in requestTimers && requestTimers[ url ] !== null )
    {
        clearTimeout( requestTimers[ url ] );
        requestTimers[ url ] = null;
    }

    return new Promise( ( resolve ) => {
        requestTimers[ url ] = setTimeout( () => {
            resolve( interruptableRequest( url, params ) );
        }, milliseconds );
    } );

}

/* ---------------------------------- */
/* filterBase62Input */

/**
 * Prevent an event from proliferating if it contains non-base62 characters
 *
 * @param {Event} event - The input event to be filtered
 */
function filterBase62Input( event )
{

    // Pull the input from the keypress event or the clipboard event
    let input = event.type === 'keypress'
        ? String.fromCharCode( event.keyCode || event.which )
        : ( event.clipboardData || window.clipboardData ).getData( 'Text' );

    // If the input contains anything non-base62, prevent the event from proliferating
    if ( /[^a-zA-Z0-9]/gi.test( input ) )
    {
        event.preventDefault();
    }

}

/* ---------------------------------- */
/* formDataFromObject */

/**
 * Return a FormData object constructed from the key value pairs in the provided object
 *
 * @param {Object} object - The object to construct the FormData object from
 *
 * @returns {FormData} The constructed FormData object
 */
function formDataFromObject( object )
{
    const formData = new FormData();
    for ( const [ key, value ] of Object.entries( object ) )
    {
        formData.append( key, value );
    }
    return formData;
}

/* ---------------------------------- */
/* interruptableRequest */

let activeRequests = {};

/**
 * Perform an interruptable GET request
 * If a previous request to the provided URL is still active,
 * it will be aborted before a new one is made.
 *
 * @param {string} url - The URL to make the request to
 * @param {string} params - A parameter string to append to the request URL
 *
 * @returns {Promise} Promise that will contain a response to the request
 */
async function interruptableRequest( url, params )
{

    if ( url in activeRequests && activeRequests[ url ] !== null )
    {
        activeRequests[ url ].abort();
        activeRequests[ url ] = null;
    }

    return m.request( {
        method: 'GET',
        url: `${ url }${ params ?? '' }`,
        config: ( xhr ) => activeRequests[ url ] = xhr
    } );

}

/* ---------------------------------- */
/* sleep */

/**
 * Return a promise that will resolve after the provided number of milliseconds
 *
 * @param {number} milliseconds - The number of milliseconds to wait before the promise will resolve
 *
 * @returns {Promise} Promise that will resolve after the provided number of milliseconds
 */
async function sleep( milliseconds ) {
    return new Promise( resolve => setTimeout( resolve, milliseconds ) );
}

/* -------------------------------------------------------------------------- */
/* Export */

export {
    cancelRequestsToURL,
    delayedInterruptableRequest,
    filterBase62Input,
    formDataFromObject,
    interruptableRequest,
    sleep
};
