const utils = require( './ext.RevisionSlider.util.js' );
/**
 * Module handling diff page reloading and the RevisionSlider browser history
 *
 * @class Diffpage
 * @param {History} [historyObj] Defaults to the global History object
 * @constructor
 */
function DiffPage( historyObj ) {
	this.lastRequest = null;

	this.history = historyObj || history;
}

Object.assign( DiffPage.prototype, {
	/**
	 * Refreshes the diff view with two given revision IDs
	 *
	 * @param {number} diff
	 * @param {number} oldid
	 * @param {SliderView} sliderView
	 * @param {number} [retryAttempt=0]
	 */
	refresh: function ( diff, oldid, sliderView, retryAttempt ) {
		const self = this,
			retryLimit = 2,
			params = this.getCurrentDiffPageParams();

		params.diff = diff;
		params.oldid = oldid;
		retryAttempt = retryAttempt || 0;

		if ( this.lastRequest ) {
			this.lastRequest.abort();
		}

		$( 'table.diff[data-mw-interface]' ).addClass( 'mw-revslider-diff-loading' );

		this.lastRequest = $.ajax( {
			url: mw.util.wikiScript( 'index' ),
			data: params,
			tryCount: 0
		} );
		// Don't chain, so lastRequest is a jQuery.jqXHR object
		this.lastRequest.then( ( data2 ) => {
			const $data = $( data2 ),
				$container = $( '.mw-revslider-container' ),
				scrollLeft = $container.find( '.mw-revslider-revisions-container' ).scrollLeft();

			// Add our current rendered slider into the newly loaded container
			$data.find( '.mw-revslider-container' ).replaceWith( $container );
			const $contentText = $data.find( '#mw-content-text' );

			// Replace elements on the page with the newly loaded elements, from top to bottom
			$( '#mw-content-text' ).replaceWith( $contentText );
			$( '.printfooter' ).replaceWith( $data.find( '.printfooter' ) );

			const $catlinks = $data.find( '.catlinks[data-mw-interface]' );
			mw.hook( 'wikipage.categories' ).fire( $catlinks );
			$( '.catlinks[data-mw-interface]' ).replaceWith( $catlinks );

			// Replace navigation menus. See also T211557
			[ '#t-permalink', '#ca-delete', '#ca-edit', '#footer-places-mobileview' ].forEach( ( selector ) => {
				$( selector ).parent().replaceWith( $data.find( selector ).parent() );
			} );

			// Update wgRevisionId (T161257), and wgDiffOldId/NewId
			mw.config.set( {
				wgRevisionId: diff,
				wgDiffOldId: oldid,
				wgDiffNewId: diff
			} );

			$( '.mw-revslider-revisions-container' ).scrollLeft( scrollLeft );

			this.addHandlersToCoreLinks( sliderView );

			// Re-trigger existing, stable core hooks under the same circumstances as in core
			mw.hook( 'wikipage.content' ).fire( $contentText );

			const $nodes = $( 'table.diff[data-mw-interface]' );
			if ( $nodes.length ) {
				mw.hook( 'wikipage.diff' ).fire( $nodes.eq( 0 ) );
			}
		}, function ( xhr ) {
			$( 'table.diff[data-mw-interface]' ).removeClass( 'mw-revslider-diff-loading' );
			if ( xhr.statusText !== 'abort' ) {
				this.tryCount++;
				utils.incrementErrorStats( 'refresh' );
				if ( retryAttempt <= retryLimit ) {
					self.refresh( diff, oldid, sliderView, retryAttempt + 1 );
				}
				// TODO notify the user that we failed to update the diff?
				// This could also attempt to reload the page with the correct diff loaded without ajax?
			}
		} );
	},

	/**
	 * Replaces the current state in the history stack
	 *
	 * @param {number} diff
	 * @param {number} oldid
	 * @param {SliderView} sliderView
	 */
	replaceState: function ( diff, oldid, sliderView ) {
		this.history.replaceState(
			this.getStateObject( diff, oldid, sliderView ),
			$( document ).find( 'title' ).text(),
			this.getStateUrl( diff, oldid )
		);
	},

	/**
	 * Pushes the current state onto the history stack
	 *
	 * @param {number} diff
	 * @param {number} oldid
	 * @param {SliderView} sliderView
	 */
	pushState: function ( diff, oldid, sliderView ) {
		this.history.pushState(
			this.getStateObject( diff, oldid, sliderView ),
			$( document ).find( 'title' ).text(),
			this.getStateUrl( diff, oldid )
		);
	},

	/**
	 * Gets a state object to be used with history.replaceState and history.pushState
	 *
	 * @private
	 * @param {number} diff
	 * @param {number} oldid
	 * @param {SliderView} sliderView
	 * @return {Object}
	 */
	getStateObject: function ( diff, oldid, sliderView ) {
		return {
			diff: diff,
			oldid: oldid,
			pointerOlderPos: sliderView.pointerOlder.getPosition(),
			pointerNewerPos: sliderView.pointerNewer.getPosition(),
			sliderPos: sliderView.slider.getOldestVisibleRevisionIndex()
		};
	},

	/**
	 * Gets a URL to be used with history.replaceState and history.pushState
	 *
	 * @private
	 * @param {number} diff
	 * @param {number} oldid
	 * @return {string}
	 */
	getStateUrl: function ( diff, oldid ) {
		const params = this.getCurrentDiffPageParams();
		params.diff = diff;
		params.oldid = oldid;
		return mw.util.wikiScript( 'index' ) + '?' + $.param( params );
	},

	/**
	 * Returns an object containing all possible parameters that should be included in diff URLs
	 * when selected revisions change, e.g. uselang
	 *
	 * @private
	 * @return {Object}
	 */
	getCurrentDiffPageParams: function () {
		const urlSearchParams = new URLSearchParams( location.search );
		// In the future this can use ES2019 'Object.fromEntries' method
		const params = {};
		for ( const [ k, v ] of urlSearchParams.entries() ) {
			params[ k ] = v;
		}
		return params;
	},

	/**
	 * @param {SliderView} sliderView
	 */
	initOnPopState: function ( sliderView ) {
		window.addEventListener( 'popstate', ( event ) => {
			if ( event.state === null ) {
				return;
			}
			require( 'ext.RevisionSlider.Slider' ).utils.incrementEventStats( 'historyChange' );
			sliderView.slider.setFirstVisibleRevisionIndex( event.state.sliderPos );
			sliderView.updatePointersAndDiffView(
				event.state.pointerNewerPos,
				event.state.pointerOlderPos,
				false
			);
		} );
	},

	/**
	 * @param {SliderView} sliderView
	 */
	addHandlersToCoreLinks: function ( sliderView ) {
		$( '#differences-nextlink' ).on( 'click', () => {
			sliderView.showNextDiff();
			return false;
		} );
		$( '#differences-prevlink' ).on( 'click', () => {
			sliderView.showPrevDiff();
			return false;
		} );
	}
} );

module.exports = DiffPage;
