import { STORE_NAME } from '@quantum-viewports/store';
import { debug } from '@quantum-viewports/utils';

const {
    data: {
        dispatch,
    },
    element: {
        useEffect,
    }
} = window[ 'wp' ];

/**
 * Global dirty and save states.
 */
let isAutosaving = false;
let isRunning: any = false;
let oldSaveEntityRecord: Function;
let oldSavePost: Function;
let oldAutosave: Function;


/**
 * Fire the given event on body and log it.
 *
 * @param name   The events name
 * @param detail Additional data passed to subscribers
 */
function dispatchEvent( name: string, detail: any ) {
    document.body.dispatchEvent( new CustomEvent( name, { detail } ) );
}


/**
 * Wrapper function for saveEntityRecord().
 *
 * @param kind
 * @param name
 * @param record
 * @param options
 * @returns
 */
function newSaveEntityRecord( kind: string, name: string, record: any[], options: object ) {

    // Bail out if autosaving.
    if ( isAutosaving ) {
        return oldSaveEntityRecord( kind, name, record, options );
    }

    // Bail out if savePost.
    if ( 'savePost' === isRunning ) {
        return oldSaveEntityRecord( kind, name, record, options );
    }

    // Set active runner flag.
    isRunning = 'saveEntityRecord';

    // Fire event before saveEntityRecord().
    dispatchEvent( 'qp-saveEntityRecord-before', { kind, name, record, options } );

    // Execute saveEnityRecord.
    const resultPromise = oldSaveEntityRecord( kind, name, record, options );

    // Return outer promise and dispatch an event when inner promise is done.
    return new Promise( ( resolve, reject ) => {
        resultPromise
        .then( ( response: any ) => {
            dispatchEvent( 'qp-saveEntityRecord-after', response );
            resolve( response );
        } )
        .catch( ( response: any ) => {
            dispatchEvent( 'qp-saveEntityRecord-after', response );
            reject( response );
        } );
    } );
}


/**
 * Wrapper function for autosave().
 *
 * @param state
 * @returns
 */
function newSavePost( options = {} ) {
    isRunning = 'savePost';

    // Fire event before autosave().
    dispatchEvent( 'qp-savePost-before', options );

    // Execute autosave.
    const resultPromise = oldSavePost( options );

    // Return outer promise and dispatch an event when inner promise is done.
    return new Promise( ( resolve, reject ) => {
        resultPromise
        .then( ( response: any ) => {
            dispatchEvent( 'qp-savePost-after', response );
            resolve( response );
        } )
        .catch( ( response: any ) => {
            dispatchEvent( 'qp-savePost-after', response );
            reject( response );
        } );
    } );
}


/**
 * Wrapper function for autosave().
 *
 * @param state
 * @returns
 */
function newAutosave( state: any ) {

    // Fire event before autosave().
    dispatchEvent( 'qp-autosave-before', state );
    isAutosaving = true;

    // Execute autosave.
    const resultPromise = oldAutosave( state );

    // Return outer promise and dispatch an event when inner promise is done.
    return new Promise( ( resolve, reject ) => {
        resultPromise
        .then( ( response: any ) => {
            dispatchEvent( 'qp-autosave-after', response );
            isAutosaving = false;
            resolve( response );
        } )
        .catch( ( response: any ) => {
            dispatchEvent( 'qp-autosave-after', response );
            isAutosaving = false;
            reject( response );
        } );
    } );
}


/**
 * Set saveMap to check the last save end call.
 */
const saveMap = new Map();


/**
 * Set function to fire on saving start.
 *
 * @param {object} event
 */
function onSaveEntityRecordStart( event ) {
    const { name, record: {
        id,
    } } = event.detail;

    debug(
        'log',
        'save',
        'onSaveEntityRecordStart',
        `${ name } -> ${ id }`
    );

    const saveKey = name + '-' + id;

    saveMap.set( saveKey, true );

    dispatch( STORE_NAME ).setSaving();
}


/**
 * Set function to fire on saving end.
 */
function onSaveEntityRecordEnd( event ) {
    const { type, id } = event.detail;

    debug(
        'log',
        'save',
        'onSaveEntityRecordEnd',
        `${ name } -> ${ id }`
    );

    const saveKey = type + '-' + id;

    saveMap.delete( saveKey );

    if ( 0 < saveMap.size ) {
        return;
    }

    debug(
        'log',
        'save',
        'onSaveEntityRecordEnd unsetSaving',
        `${ name } -> ${ id }`
    );

    isRunning = false;

    // Update all blocks to viewports valid attributes and unset saving.
    dispatch( STORE_NAME ).unsetSaving();
}


/**
 * Set function to fire on savePost start.
 */
function onSavePostStart() {
    if ( isAutosaving ) {
        return;
    }

    debug(
        'log',
        'save',
        'onSavePostStart',
    );

    dispatch( STORE_NAME ).setSaving();
}


/**
 * Set function to fire on savePost end.
 */
function onSavePostEnd() {
    if ( isAutosaving ) {
        return;
    }

    isRunning = false;

    debug(
        'log',
        'save',
        'onSavePostEnd',
    );

    // Update all blocks to viewports valid attributes and unset saving.
    dispatch( STORE_NAME ).unsetSaving();
}


/**
 * Set function to fire on saving start.
 */
function onAutoSavingStart() {
    debug(
        'log',
        'save',
        'onAutoSavingStart',
    );

    dispatch( STORE_NAME ).setAutoSaving();
}


/**
 * Set function to fire on autosaving end.
 */
function onAutoSavingEnd() {
    debug(
        'log',
        'save',
        'onAutoSavingEnd',
    );

    // Update all blocks to viewport valid attributes and unset saving.
    dispatch( STORE_NAME ).unsetAutoSaving();
}


/**
 * Export component that fires events for dirty and saving state changes.
 */
export function Save() {

    /**
     * Set useEffect to handle onMount event registration onMount and onUnmount.
     */
    useEffect( () => {
        document.body.addEventListener( 'qp-saveEntityRecord-before', onSaveEntityRecordStart );
        document.body.addEventListener( 'qp-saveEntityRecord-after', onSaveEntityRecordEnd );

        document.body.addEventListener( 'qp-savePost-before', onSavePostStart );
        document.body.addEventListener( 'qp-savePost-after', onSavePostEnd );

        document.body.addEventListener( 'qp-autosave-before', onAutoSavingStart );
        document.body.addEventListener( 'qp-autosave-after', onAutoSavingEnd );

        return () => {
            document.body.removeEventListener( 'qp-saveEntityRecord-before', onSaveEntityRecordStart );
            document.body.removeEventListener( 'qp-saveEntityRecord-after', onSaveEntityRecordEnd );

            document.body.removeEventListener( 'qp-savePost-before', onSavePostStart );
            document.body.removeEventListener( 'qp-savePost-after', onSavePostEnd );

            document.body.removeEventListener( 'qp-autosave-before', onAutoSavingStart );
            document.body.removeEventListener( 'qp-autosave-after', onAutoSavingEnd );
        }
    }, [] );

    // Hook into saveEntityRecord.
    const coreStore = dispatch( 'core' );
    if ( coreStore && ! oldSaveEntityRecord ) {
        oldSaveEntityRecord = coreStore.saveEntityRecord;
        coreStore.saveEntityRecord = newSaveEntityRecord;
    }

    // Hook into autosave.
    const coreEditorStore = dispatch( 'core/editor' );
    if ( coreEditorStore && ! oldAutosave ) {
        oldAutosave = coreEditorStore.autosave;
        coreEditorStore.autosave = newAutosave;

        oldSavePost = coreEditorStore.savePost;
        coreEditorStore.savePost = newSavePost;
    }

    // Return nothing.
    return null;
}
