/**
 * Configuration of Dialog module for wikiEditor
 */
( function ( $, mw, OO ) {

	var hasOwn = Object.prototype.hasOwnProperty;

	$.wikiEditor.modules.dialogs.config = {

		replaceIcons: function ( $textarea ) {
			$textarea
				.wikiEditor( 'addToToolbar', {
					section: 'main',
					group: 'insert',
					tools: {
						link: {
							labelMsg: 'wikieditor-toolbar-tool-link',
							type: 'button',
							oouiIcon: 'link',
							action: {
								type: 'dialog',
								module: 'insert-link'
							}
						},
						file: {
							labelMsg: 'wikieditor-toolbar-tool-file',
							type: 'button',
							oouiIcon: 'image',
							action: {
								type: 'dialog',
								module: 'insert-file'
							}
						},
						reference: {
							labelMsg: 'wikieditor-toolbar-tool-reference',
							filters: [ 'body.ns-subject' ],
							type: 'button',
							oouiIcon: 'reference',
							action: {
								type: 'dialog',
								module: 'insert-reference'
							}
						}
					}
				} )
				.wikiEditor( 'addToToolbar', {
					section: 'advanced',
					group: 'insert',
					tools: {
						table: {
							labelMsg: 'wikieditor-toolbar-tool-table',
							type: 'button',
							oouiIcon: 'table',
							action: {
								type: 'dialog',
								module: 'insert-table'
							}
						}
					}
				} )
				.wikiEditor( 'addToToolbar', {
					section: 'advanced',
					groups: {
						search: {
							tools: {
								replace: {
									labelMsg: 'wikieditor-toolbar-tool-replace',
									type: 'button',
									oouiIcon: 'articleSearch',
									action: {
										type: 'dialog',
										module: 'search-and-replace'
									}
								}
							}
						}
					}
				} );
		},

		getDefaultConfig: function () {
			return { dialogs: {
				'insert-link': {
					titleMsg: 'wikieditor-toolbar-tool-link-title',
					id: 'wikieditor-toolbar-link-dialog',
					htmlTemplate: 'dialogInsertLink.html',

					init: function () {
						var api = new mw.Api();

						function isExternalLink( s ) {
							// The following things are considered to be external links:
							// * Starts with a URL protocol
							// * Starts with www.
							// All of these are potentially valid titles, and the latter two categories match about 6300
							// titles in enwiki's ns0. Out of 6.9M titles, that's 0.09%
							/* eslint-disable no-caller */
							if ( typeof arguments.callee.regex === 'undefined' ) {
								// Cache the regex
								arguments.callee.regex =
									new RegExp( '^(' + mw.config.get( 'wgUrlProtocols' ) + '|www\\.)', 'i' );
							}
							return s.match( arguments.callee.regex );
							/* eslint-enable no-caller */
						}

						// Updates the status indicator above the target link
						function updateWidget( status, reason ) {
							$( '#wikieditor-toolbar-link-int-target-status' ).children().hide();
							$( '#wikieditor-toolbar-link-int-target' ).parent()
								.removeClass(
									'status-invalid status-external status-notexists status-exists status-loading'
								);
							if ( status ) {
								$( '#wikieditor-toolbar-link-int-target-status-' + status ).show();
								$( '#wikieditor-toolbar-link-int-target' ).parent().addClass( 'status-' + status );
							}
							if ( status === 'invalid' ) {
								$( '.ui-dialog:visible .ui-dialog-buttonpane button:first' )
									.prop( 'disabled', true )
									.addClass( 'disabled' );
								if ( reason ) {
									$( '#wikieditor-toolbar-link-int-target-status-invalid' ).html( reason );
								} else {
									$( '#wikieditor-toolbar-link-int-target-status-invalid' )
										.text( mw.msg( 'wikieditor-toolbar-tool-link-int-target-status-invalid' ) );
								}

							} else {
								$( '.ui-dialog:visible .ui-dialog-buttonpane button:first' )
									.prop( 'disabled', false )
									.removeClass( 'disabled' );
							}
						}

						// Updates the UI to show if the page title being inputted by the user exists or not
						// accepts parameter internal for bypassing external link detection
						function updateExistence( internal ) {
							// Abort previous request
							var request = $( '#wikieditor-toolbar-link-int-target-status' ).data( 'request' ),
								target = $( '#wikieditor-toolbar-link-int-target' ).val(),
								cache = $( '#wikieditor-toolbar-link-int-target-status' ).data( 'existencecache' ),
								reasoncache = $( '#wikieditor-toolbar-link-int-target-status' ).data( 'reasoncache' );
							// ensure the internal parameter is a boolean
							if ( internal !== true ) {
								internal = false;
							}
							if ( request ) {
								request.abort();
							}
							if ( hasOwn.call( cache, target ) ) {
								updateWidget( cache[ target ], reasoncache[ target ] );
								return;
							}
							if ( target.replace( /^\s+$/, '' ) === '' ) {
								// Hide the widget when the textbox is empty
								updateWidget( false );
								return;
							}
							// If the forced internal parameter was not true, check if the target is an external link
							if ( !internal && isExternalLink( target ) ) {
								updateWidget( 'external' );
								return;
							}
							// Show loading spinner while waiting for the API to respond
							updateWidget( 'loading' );
							// Call the API to check page status, saving the request object so it can be aborted if
							// necessary.
							// This used to request a page that would show whether or not the target exists, but we can
							// also check whether it has the disambiguation property and still get existence information.
							// If the Disambiguator extension is not installed then such a property won't be set.
							$( '#wikieditor-toolbar-link-int-target-status' ).data(
								'request',
								api.get( {
									formatversion: 2,
									action: 'query',
									prop: 'pageprops',
									titles: [ target ],
									ppprop: 'disambiguation',
									errorformat: 'html',
									errorlang: mw.config.get( 'wgUserLanguage' )
								} ).done( function ( data ) {
									var status, page, reason = null;
									if ( !data.query || !data.query.pages ) {
										// This happens in some weird cases like interwiki links
										status = false;
									} else {
										page = data.query.pages[ 0 ];
										status = 'exists';
										if ( page.missing ) {
											status = 'notexists';
										} else if ( page.invalid ) {
											status = 'invalid';
											reason = page.invalidreason && page.invalidreason.html;
										} else if ( page.pageprops ) {
											status = 'disambig';
										}
									}
									// Cache the status of the link target if the force internal
									// parameter was not passed
									if ( !internal ) {
										cache[ target ] = status;
										reasoncache[ target ] = reason;
									}
									updateWidget( status, reason );
								} )
							);
						}
						$( '#wikieditor-toolbar-link-type-int, #wikieditor-toolbar-link-type-ext' ).click( function () {
							var request;
							if ( $( '#wikieditor-toolbar-link-type-ext' ).prop( 'checked' ) ) {
								// Abort previous request
								request = $( '#wikieditor-toolbar-link-int-target-status' ).data( 'request' );
								if ( request ) {
									request.abort();
								}
								updateWidget( 'external' );
							}
							if ( $( '#wikieditor-toolbar-link-type-int' ).prop( 'checked' ) ) {
								updateExistence( true );
							}
						} );
						// Set labels of tabs based on rel values
						$( this ).find( '[rel]' ).each( function () {
							$( this ).text( mw.msg( $( this ).attr( 'rel' ) ) );
						} );
						// Set tabindexes on form fields
						$.wikiEditor.modules.dialogs.fn.setTabindexes( $( this ).find( 'input' ).not( '[tabindex]' ) );
						// Setup the tooltips in the textboxes
						$( '#wikieditor-toolbar-link-int-target' )
							.data( 'tooltip', mw.msg( 'wikieditor-toolbar-tool-link-int-target-tooltip' ) );
						$( '#wikieditor-toolbar-link-int-text' )
							.data( 'tooltip', mw.msg( 'wikieditor-toolbar-tool-link-int-text-tooltip' ) );
						$( '#wikieditor-toolbar-link-int-target, #wikieditor-toolbar-link-int-text' )
							.each( function () {
								if ( $( this ).val() === '' ) {
									$( this )
										.addClass( 'wikieditor-toolbar-dialog-hint' )
										.val( $( this ).data( 'tooltip' ) )
										.data( 'tooltip-mode', true );
								}
							} )
							.on( 'focus', function () {
								if ( $( this ).val() === $( this ).data( 'tooltip' ) ) {
									$( this )
										.val( '' )
										.removeClass( 'wikieditor-toolbar-dialog-hint' )
										.data( 'tooltip-mode', false );
								}
							} )
							.on( 'change', function () {
								if ( $( this ).val() !== $( this ).data( 'tooltip' ) ) {
									$( this )
										.removeClass( 'wikieditor-toolbar-dialog-hint' )
										.data( 'tooltip-mode', false );
								}
							} )
							.on( 'blur', function () {
								if ( $( this ).val() === '' ) {
									$( this )
										.addClass( 'wikieditor-toolbar-dialog-hint' )
										.val( $( this ).data( 'tooltip' ) )
										.data( 'tooltip-mode', true );
								}
							} );

						// Automatically copy the value of the internal link page title field to the link text field unless the
						// user has changed the link text field - this is a convenience thing since most link texts are going to
						// be the same as the page title - Also change the internal/external radio button accordingly
						$( '#wikieditor-toolbar-link-int-target' ).on( 'change keydown paste cut', function () {
							// $( this ).val() is the old value, before the keypress - Defer this until $( this ).val() has
							// been updated
							setTimeout( function () {
								if ( isExternalLink( $( '#wikieditor-toolbar-link-int-target' ).val() ) ) {
									$( '#wikieditor-toolbar-link-type-ext' ).prop( 'checked', true );
									updateWidget( 'external' );
								} else {
									$( '#wikieditor-toolbar-link-type-int' ).prop( 'checked', true );
									updateExistence();
								}
								if ( $( '#wikieditor-toolbar-link-int-text' ).data( 'untouched' ) ) {
									// eslint-disable-next-line eqeqeq
									if ( $( '#wikieditor-toolbar-link-int-target' ).val() ==
										$( '#wikieditor-toolbar-link-int-target' ).data( 'tooltip' )
									) {
										$( '#wikieditor-toolbar-link-int-text' )
											.addClass( 'wikieditor-toolbar-dialog-hint' )
											.val( $( '#wikieditor-toolbar-link-int-text' ).data( 'tooltip' ) )
											.change();
									} else {
										$( '#wikieditor-toolbar-link-int-text' )
											.val( $( '#wikieditor-toolbar-link-int-target' ).val() )
											.change();
									}
								}
							}, 0 );
						} );
						$( '#wikieditor-toolbar-link-int-text' ).on( 'change keydown paste cut', function () {
							var oldVal = $( this ).val(),
								that = this;
							setTimeout( function () {
								if ( $( that ).val() !== oldVal ) {
									$( that ).data( 'untouched', false );
								}
							}, 0 );
						} );
						// Add images to the page existence widget, which will be shown mutually exclusively to communicate if
						// the page exists, does not exist or the title is invalid (like if it contains a | character)
						$( '#wikieditor-toolbar-link-int-target-status' )
							.append( $( '<div>' )
								.attr( 'id', 'wikieditor-toolbar-link-int-target-status-exists' )
								.text( mw.msg( 'wikieditor-toolbar-tool-link-int-target-status-exists' ) )
							)
							.append( $( '<div>' )
								.attr( 'id', 'wikieditor-toolbar-link-int-target-status-notexists' )
								.text( mw.msg( 'wikieditor-toolbar-tool-link-int-target-status-notexists' ) )
							)
							.append( $( '<div>' )
								.attr( 'id', 'wikieditor-toolbar-link-int-target-status-invalid' )
							)
							.append( $( '<div>' )
								.attr( 'id', 'wikieditor-toolbar-link-int-target-status-external' )
								.text( mw.msg( 'wikieditor-toolbar-tool-link-int-target-status-external' ) )
							)
							.append( $( '<div>' )
								.attr( 'id', 'wikieditor-toolbar-link-int-target-status-loading' )
								.attr( 'title', mw.msg( 'wikieditor-toolbar-tool-link-int-target-status-loading' ) )
							)
							.append( $( '<div>' )
								.attr( 'id', 'wikieditor-toolbar-link-int-target-status-disambig' )
								.text( mw.msg( 'wikieditor-toolbar-tool-link-int-target-status-disambig' ) )
							)
							.data( 'existencecache', {} )
							.data( 'reasoncache', {} )
							.children().hide();

						$( '#wikieditor-toolbar-link-int-target' )
							.on( 'keyup paste cut', function () {
								var timerID;
								// Cancel the running timer if applicable
								if ( typeof $( this ).data( 'timerID' ) !== 'undefined' ) {
									clearTimeout( $( this ).data( 'timerID' ) );
								}
								// Delay fetch for a while
								// FIXME: Make 120 configurable elsewhere
								timerID = setTimeout( updateExistence, 120 );
								$( this ).data( 'timerID', timerID );
							} )
							.on( 'change', function () {
								// Cancel the running timer if applicable
								if ( typeof $( this ).data( 'timerID' ) !== 'undefined' ) {
									clearTimeout( $( this ).data( 'timerID' ) );
								}
								// Fetch right now
								updateExistence();
							} );

						// Title suggestions
						$( '#wikieditor-toolbar-link-int-target' ).data( 'suggcache', {} ).suggestions( {
							fetch: function () {
								var cache, request,
									that = this,
									title = $( this ).val();

								if ( isExternalLink( title ) || title.indexOf( '|' ) !== -1 || title === '' ) {
									$( this ).suggestions( 'suggestions', [] );
									return;
								}

								cache = $( this ).data( 'suggcache' );
								if ( hasOwn.call( cache, title ) ) {
									$( this ).suggestions( 'suggestions', cache[ title ] );
									return;
								}

								request = api.get( {
									formatversion: 2,
									action: 'opensearch',
									search: title,
									namespace: 0,
									suggest: ''
								} ).done( function ( data ) {
									cache[ title ] = data[ 1 ];
									$( that ).suggestions( 'suggestions', data[ 1 ] );
								} );
								$( this ).data( 'request', request );
							},
							cancel: function () {
								var request = $( this ).data( 'request' );
								if ( request ) {
									request.abort();
								}
							}
						} );
					},
					dialog: {
						width: 500,
						dialogClass: 'wikiEditor-toolbar-dialog',
						buttons: {
							'wikieditor-toolbar-tool-link-insert': function () {
								var match, buttons, escTarget, escText,
									that = this,
									insertText = '',
									whitespace = $( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace' ),
									target = $( '#wikieditor-toolbar-link-int-target' ).val(),
									text = $( '#wikieditor-toolbar-link-int-text' ).val();

								function escapeInternalText( s ) {
									return s.replace( /(\]{2,})/g, '<nowiki>$1</nowiki>' );
								}
								function escapeExternalTarget( s ) {
									return s.replace( / /g, '%20' )
										.replace( /\[/g, '%5B' )
										.replace( /\]/g, '%5D' );
								}
								function escapeExternalText( s ) {
									return s.replace( /(\]+)/g, '<nowiki>$1</nowiki>' );
								}
								// check if the tooltips were passed as target or text
								if ( $( '#wikieditor-toolbar-link-int-target' ).data( 'tooltip-mode' ) ) {
									target = '';
								}
								if ( $( '#wikieditor-toolbar-link-int-text' ).data( 'tooltip-mode' ) ) {
									text = '';
								}
								if ( target === '' ) {
									// eslint-disable-next-line no-alert
									alert( mw.msg( 'wikieditor-toolbar-tool-link-empty' ) );
									return;
								}
								if ( $.trim( text ) === '' ) {
									// [[Foo| ]] creates an invisible link
									// Instead, generate [[Foo|]]
									text = '';
								}
								if ( $( '#wikieditor-toolbar-link-type-int' ).is( ':checked' ) ) {
									// FIXME: Exactly how fragile is this?
									if ( $( '#wikieditor-toolbar-link-int-target-status-invalid' ).is( ':visible' ) ) {
										// Refuse to add links to invalid titles
										// eslint-disable-next-line no-alert
										alert( mw.msg( 'wikieditor-toolbar-tool-link-int-invalid' ) );
										return;
									}

									if ( target === text || !text.length ) {
										insertText = '[[' + target + ']]';
									} else {
										insertText = '[[' + target + '|' + escapeInternalText( text ) + ']]';
									}
								} else {
									target = $.trim( target );
									// Prepend http:// if there is no protocol
									if ( !target.match( /^[a-z]+:\/\/./ ) ) {
										target = 'http://' + target;
									}

									// Detect if this is really an internal link in disguise
									match = target.match( $( this ).data( 'articlePathRegex' ) );
									if ( match && !$( this ).data( 'ignoreLooksInternal' ) ) {
										buttons = {};
										buttons[ mw.msg( 'wikieditor-toolbar-tool-link-lookslikeinternal-int' ) ] =
											function () {
												$( '#wikieditor-toolbar-link-int-target' ).val( match[ 1 ] ).change();
												$( this ).dialog( 'close' );
											};
										buttons[ mw.msg( 'wikieditor-toolbar-tool-link-lookslikeinternal-ext' ) ] =
											function () {
												$( that ).data( 'ignoreLooksInternal', true );
												$( that ).closest( '.ui-dialog' ).find( 'button:first' ).click();
												$( that ).data( 'ignoreLooksInternal', false );
												$( this ).dialog( 'close' );
											};
										$.wikiEditor.modules.dialogs.quickDialog(
											mw.msg( 'wikieditor-toolbar-tool-link-lookslikeinternal', match[ 1 ] ),
											{ buttons: buttons }
										);
										return;
									}

									escTarget = escapeExternalTarget( target );
									escText = escapeExternalText( text );

									if ( escTarget === escText ) {
										insertText = escTarget;
									} else if ( text === '' ) {
										insertText = '[' + escTarget + ']';
									} else {
										insertText = '[' + escTarget + ' ' + escText + ']';
									}
								}
								// Preserve whitespace in selection when replacing
								if ( whitespace ) {
									insertText = whitespace[ 0 ] + insertText + whitespace[ 1 ];
								}
								$( this ).dialog( 'close' );
								$.wikiEditor.modules.toolbar.fn.doAction( $( this ).data( 'context' ), {
									type: 'replace',
									options: {
										pre: insertText
									}
								}, $( this ) );

								// Blank form
								$( '#wikieditor-toolbar-link-int-target, #wikieditor-toolbar-link-int-text' ).val( '' );
								$( '#wikieditor-toolbar-link-type-int, #wikieditor-toolbar-link-type-ext' )
									.prop( 'checked', false );
							},
							'wikieditor-toolbar-tool-link-cancel': function () {
								$( this ).dialog( 'close' );
							}
						},
						open: function () {
							var target, text, type, matches, context, selection,
								// Obtain the server name without the protocol. wgServer may be protocol-relative
								serverName = mw.config.get( 'wgServer' ).replace( /^(https?:)?\/\//, '' );
							// Cache the articlepath regex
							$( this ).data( 'articlePathRegex', new RegExp(
								'^https?://' + mw.RegExp.escape( serverName + mw.config.get( 'wgArticlePath' ) )
									.replace( /\\\$1/g, '(.*)' ) + '$'
							) );
							// Pre-fill the text fields based on the current selection
							context = $( this ).data( 'context' );
							selection = context.$textarea.textSelection( 'getSelection' );
							$( '#wikieditor-toolbar-link-int-target' ).focus();
							// Trigger the change event, so the link status indicator is up to date
							$( '#wikieditor-toolbar-link-int-target' ).change();
							$( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [ '', '' ] );
							if ( selection !== '' ) {
								if ( ( matches = selection.match( /^(\s*)\[\[([^\]|]+)(\|([^\]|]*))?\]\](\s*)$/ ) ) ) {
									// [[foo|bar]] or [[foo]]
									target = matches[ 2 ];
									text = ( matches[ 4 ] ? matches[ 4 ] : matches[ 2 ] );
									type = 'int';
									// Preserve whitespace when replacing
									$( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [ matches[ 1 ], matches[ 5 ] ] );
								} else if ( ( matches = selection.match( /^(\s*)\[([^\] ]+)( ([^\]]+))?\](\s*)$/ ) ) ) {
									// [http://www.example.com foo] or [http://www.example.com]
									target = matches[ 2 ];
									text = ( matches[ 4 ] || '' );
									type = 'ext';
									// Preserve whitespace when replacing
									$( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [ matches[ 1 ], matches[ 5 ] ] );
								} else {
									// Trim any leading and trailing whitespace from the selection,
									// but preserve it when replacing
									target = text = $.trim( selection );
									if ( target.length < selection.length ) {
										$( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [
											selection.substr( 0, selection.indexOf( target.charAt( 0 ) ) ),
											selection.substr(
												selection.lastIndexOf( target.charAt( target.length - 1 ) ) + 1
											) ]
										);
									}
								}

								// Change the value by calling val() doesn't trigger the change event, so let's do that
								// ourselves
								if ( typeof text !== 'undefined' ) {
									$( '#wikieditor-toolbar-link-int-text' ).val( text ).change();
								}
								if ( typeof target !== 'undefined' ) {
									$( '#wikieditor-toolbar-link-int-target' ).val( target ).change();
								}
								if ( typeof type !== 'undefined' ) {
									$( '#wikieditor-toolbar-link-' + type ).prop( 'checked', true );
								}
							}
							$( '#wikieditor-toolbar-link-int-text' ).data( 'untouched',
								$( '#wikieditor-toolbar-link-int-text' ).val() ===
										$( '#wikieditor-toolbar-link-int-target' ).val() ||
									$( '#wikieditor-toolbar-link-int-text' ).hasClass( 'wikieditor-toolbar-dialog-hint' )
							);
							$( '#wikieditor-toolbar-link-int-target' ).suggestions();

							// don't overwrite user's text
							if ( selection !== '' ) {
								$( '#wikieditor-toolbar-link-int-text' ).data( 'untouched', false );
							}

							$( '#wikieditor-toolbar-link-int-text, #wikiedit-toolbar-link-int-target' )
								.each( function () {
									if ( $( this ).val() === '' ) {
										$( this ).parent().find( 'label' ).show();
									}
								} );

							if ( !$( this ).data( 'dialogkeypressset' ) ) {
								$( this ).data( 'dialogkeypressset', true );
								// Execute the action associated with the first button
								// when the user presses Enter
								$( this ).closest( '.ui-dialog' ).keypress( function ( e ) {
									var button;
									if ( ( e.keyCode || e.which ) === 13 ) {
										button = $( this ).data( 'dialogaction' ) || $( this ).find( 'button:first' );
										button.click();
										e.preventDefault();
									}
								} );

								// Make tabbing to a button and pressing
								// Enter do what people expect
								$( this ).closest( '.ui-dialog' ).find( 'button' ).focus( function () {
									$( this ).closest( '.ui-dialog' ).data( 'dialogaction', this );
								} );
							}
						}
					}
				},
				'insert-reference': {
					titleMsg: 'wikieditor-toolbar-tool-reference-title',
					id: 'wikieditor-toolbar-reference-dialog',
					htmlTemplate: 'dialogInsertReference.html',
					init: function () {
						// Insert translated strings into labels
						$( this ).find( '[rel]' ).each( function () {
							$( this ).text( mw.msg( $( this ).attr( 'rel' ) ) );
						} );

					},
					dialog: {
						dialogClass: 'wikiEditor-toolbar-dialog',
						width: 590,
						buttons: {
							'wikieditor-toolbar-tool-reference-insert': function () {
								var insertText = $( '#wikieditor-toolbar-reference-text' ).val(),
									whitespace = $( '#wikieditor-toolbar-reference-dialog' ).data( 'whitespace' ),
									attributes = $( '#wikieditor-toolbar-reference-dialog' ).data( 'attributes' );
								// Close the dialog
								$( this ).dialog( 'close' );
								$.wikiEditor.modules.toolbar.fn.doAction(
									$( this ).data( 'context' ),
									{
										type: 'replace',
										options: {
											pre: whitespace[ 0 ] + '<ref' + attributes + '>',
											peri: insertText,
											post: '</ref>' + whitespace[ 1 ]
										}
									},
									$( this )
								);
								// Restore form state
								$( '#wikieditor-toolbar-reference-text' ).val( '' );
							},
							'wikieditor-toolbar-tool-reference-cancel': function () {
								$( this ).dialog( 'close' );
							}
						},
						open: function () {
							// Pre-fill the text fields based on the current selection
							var matches, text,
								context = $( this ).data( 'context' ),
								selection = context.$textarea.textSelection( 'getSelection' );
							// set focus
							$( '#wikieditor-toolbar-reference-text' ).focus();
							$( '#wikieditor-toolbar-reference-dialog' )
								.data( 'whitespace', [ '', '' ] )
								.data( 'attributes', '' );
							if ( selection !== '' ) {
								if ( ( matches = selection.match( /^(\s*)<ref([^>]*)>([^<]*)<\/ref>(\s*)$/ ) ) ) {
									text = matches[ 3 ];
									// Preserve whitespace when replacing
									$( '#wikieditor-toolbar-reference-dialog' )
										.data( 'whitespace', [ matches[ 1 ], matches[ 4 ] ] );
									$( '#wikieditor-toolbar-reference-dialog' ).data( 'attributes', matches[ 2 ] );
								} else {
									text = selection;
								}
								$( '#wikieditor-toolbar-reference-text' ).val( text );
							}
							if ( !( $( this ).data( 'dialogkeypressset' ) ) ) {
								$( this ).data( 'dialogkeypressset', true );
								// Execute the action associated with the first button
								// when the user presses Enter
								$( this ).closest( '.ui-dialog' ).keypress( function ( e ) {
									var button;
									if ( ( e.keyCode || e.which ) === 13 ) {
										button = $( this ).data( 'dialogaction' ) || $( this ).find( 'button:first' );
										button.click();
										e.preventDefault();
									}
								} );
								// Make tabbing to a button and pressing
								// Enter do what people expect
								$( this ).closest( '.ui-dialog' ).find( 'button' ).focus( function () {
									$( this ).closest( '.ui-dialog' ).data( 'dialogaction', this );
								} );
							}
						}
					}
				},
				'insert-file': {
					titleMsg: 'wikieditor-toolbar-tool-file-title',
					id: 'wikieditor-toolbar-file-dialog',
					htmlTemplate: 'dialogInsertFile.html',
					init: function () {
						var magicWordsI18N = mw.config.get( 'wgWikiEditorMagicWords' ),
							defaultMsg = mw.msg( 'wikieditor-toolbar-file-default' );
						$( this ).find( '[data-i18n-magic]' )
							.text( function () {
								return magicWordsI18N[ $( this ).attr( 'data-i18n-magic' ) ];
							} )
							.removeAttr( 'data-i18n-magic' );
						$( this ).find( '#wikieditor-toolbar-file-size' )
							.attr( 'placeholder', defaultMsg )
							// The message may be long in some languages
							.attr( 'size', defaultMsg.length );
						$( this ).find( '[rel]' )
							.text( function () {
								return mw.msg( $( this ).attr( 'rel' ) );
							} )
							.removeAttr( 'rel' );
					},
					dialog: {
						resizable: false,
						dialogClass: 'wikiEditor-toolbar-dialog',
						width: 590,
						buttons: {
							'wikieditor-toolbar-tool-file-insert': function () {
								var fileName, caption, fileFloat, fileFormat, fileSize, fileTitle,
									options, fileUse,
									hasPxRgx = /.+px$/,
									magicWordsI18N = mw.config.get( 'wgWikiEditorMagicWords' );
								fileName = $( '#wikieditor-toolbar-file-target' ).val();
								caption = $( '#wikieditor-toolbar-file-caption' ).val();
								fileFloat = $( '#wikieditor-toolbar-file-float' ).val();
								fileFormat = $( '#wikieditor-toolbar-file-format' ).val();
								fileSize = $( '#wikieditor-toolbar-file-size' ).val();
								// Append px to end to size if not already contains it
								if ( fileSize !== '' && !hasPxRgx.test( fileSize ) ) {
									fileSize += 'px';
								}
								if ( fileName !== '' ) {
									fileTitle = new mw.Title( fileName );
									// Append file namespace prefix to filename if not already contains it
									if ( fileTitle.getNamespaceId() !== 6 ) {
										fileTitle = new mw.Title( fileName, 6 );
									}
									fileName = fileTitle.toText();
								}
								options = [ fileSize, fileFormat, fileFloat ];
								// Filter empty values
								options = $.grep( options, function ( val ) {
									return val.length && val !== 'default';
								} );
								if ( caption.length ) {
									options.push( caption );
								}
								fileUse = options.length === 0 ? fileName : ( fileName + '|' + options.join( '|' ) );
								$( this ).dialog( 'close' );
								$.wikiEditor.modules.toolbar.fn.doAction(
									$( this ).data( 'context' ),
									{
										type: 'replace',
										options: {
											pre: '[[',
											peri: fileUse,
											post: ']]',
											ownline: true
										}
									},
									$( this )
								);

								// Restore form state
								$( [ '#wikieditor-toolbar-file-target',
									'#wikieditor-toolbar-file-caption',
									'#wikieditor-toolbar-file-size' ].join( ',' )
								).val( '' );
								$( '#wikieditor-toolbar-file-float' ).val( 'default' );
								$( '#wikieditor-toolbar-file-format' ).val( magicWordsI18N.img_thumbnail );
							},
							'wikieditor-toolbar-tool-file-cancel': function () {
								$( this ).dialog( 'close' );
							},
							'wikieditor-toolbar-tool-file-upload': function () {
								var windowManager = new OO.ui.WindowManager(),
									uploadDialog = new mw.Upload.Dialog( {
										bookletClass: mw.ForeignStructuredUpload.BookletLayout
									} );

								$( this ).dialog( 'close' );
								$( 'body' ).append( windowManager.$element );
								windowManager.addWindows( [ uploadDialog ] );
								windowManager.openWindow( uploadDialog );

								uploadDialog.uploadBooklet.on( 'fileSaved', function ( imageInfo ) {
									uploadDialog.close();
									windowManager.$element.remove();

									$.wikiEditor.modules.dialogs.api.openDialog( this, 'insert-file' );
									$( '#wikieditor-toolbar-file-target' ).val( imageInfo.canonicaltitle );
								} );
							}
						},
						open: function () {
							$( '#wikieditor-toolbar-file-target' ).focus();
							if ( !( $( this ).data( 'dialogkeypressset' ) ) ) {
								$( this ).data( 'dialogkeypressset', true );
								// Execute the action associated with the first button
								// when the user presses Enter
								$( this ).closest( '.ui-dialog' ).keypress( function ( e ) {
									var button;
									if ( e.which === 13 ) {
										button = $( this ).data( 'dialogaction' ) ||
											$( this ).find( 'button:first' );
										button.click();
										e.preventDefault();
									}
								} );

								// Make tabbing to a button and pressing
								// Enter do what people expect
								$( this ).closest( '.ui-dialog' ).find( 'button' ).focus( function () {
									$( this ).closest( '.ui-dialog' ).data( 'dialogaction', this );
								} );
							}
						}
					}
				},
				'insert-table': {
					titleMsg: 'wikieditor-toolbar-tool-table-title',
					id: 'wikieditor-toolbar-table-dialog',
					htmlTemplate: 'dialogInsertTable.html',
					init: function () {
						$( this ).find( '[rel]' ).each( function () {
							$( this ).text( mw.msg( $( this ).attr( 'rel' ) ) );
						} );
						// Set tabindexes on form fields
						$.wikiEditor.modules.dialogs.fn.setTabindexes( $( this ).find( 'input' ).not( '[tabindex]' ) );

						$( '#wikieditor-toolbar-table-dimensions-rows' ).val( 3 );
						$( '#wikieditor-toolbar-table-dimensions-columns' ).val( 3 );
						$( '#wikieditor-toolbar-table-wikitable' ).click( function () {
							$( '.wikieditor-toolbar-table-preview' ).toggleClass( 'wikitable' );
						} );

						// Hack for sortable preview: dynamically adding
						// sortable class doesn't work, so we use a clone
						$( '#wikieditor-toolbar-table-preview' )
							.clone()
							.attr( 'id', 'wikieditor-toolbar-table-preview2' )
							.addClass( 'sortable' )
							.insertAfter( $( '#wikieditor-toolbar-table-preview' ) )
							.hide();

						mw.loader.using( 'jquery.tablesorter', function () {
							$( '#wikieditor-toolbar-table-preview2' ).tablesorter();
						} );

						$( '#wikieditor-toolbar-table-sortable' ).click( function () {
							// Swap the currently shown one clone with the other one
							$( '#wikieditor-toolbar-table-preview' )
								.hide()
								.attr( 'id', 'wikieditor-toolbar-table-preview3' );
							$( '#wikieditor-toolbar-table-preview2' )
								.attr( 'id', 'wikieditor-toolbar-table-preview' )
								.show();
							$( '#wikieditor-toolbar-table-preview3' ).attr( 'id', 'wikieditor-toolbar-table-preview2' );
						} );

						$( '#wikieditor-toolbar-table-dimensions-header' ).click( function () {
							// Instead of show/hiding, switch the HTML around
							// We do this because the sortable tables script styles the first row,
							// visible or not
							var $sortable,
								headerHTML = $( '.wikieditor-toolbar-table-preview-header' ).html(),
								hiddenHTML = $( '.wikieditor-toolbar-table-preview-hidden' ).html();
							$( '.wikieditor-toolbar-table-preview-header' ).html( hiddenHTML );
							$( '.wikieditor-toolbar-table-preview-hidden' ).html( headerHTML );
							$sortable = $( '#wikieditor-toolbar-table-preview, #wikieditor-toolbar-table-preview2' )
								.filter( '.sortable' );
							mw.loader.using( 'jquery.tablesorter', function () {
								$sortable.tablesorter();
							} );
						} );
					},
					dialog: {
						resizable: false,
						dialogClass: 'wikiEditor-toolbar-dialog',
						width: 590,
						buttons: {
							'wikieditor-toolbar-tool-table-insert': function () {
								var headerText, normalText, table, r, c,
									isHeader, delim, classes, classStr,
									rowsVal = $( '#wikieditor-toolbar-table-dimensions-rows' ).val(),
									colsVal = $( '#wikieditor-toolbar-table-dimensions-columns' ).val(),
									rows = parseInt( rowsVal, 10 ),
									cols = parseInt( colsVal, 10 ),
									header = $( '#wikieditor-toolbar-table-dimensions-header' ).prop( 'checked' ) ? 1 : 0;
								if ( isNaN( rows ) || isNaN( cols ) || String( rows ) !== rowsVal || String( cols ) !== colsVal || rowsVal < 0 || colsVal < 0 ) {
									// eslint-disable-next-line no-alert
									alert( mw.msg( 'wikieditor-toolbar-tool-table-invalidnumber' ) );
									return;
								}
								if ( rows + header === 0 || cols === 0 ) {
									// eslint-disable-next-line no-alert
									alert( mw.msg( 'wikieditor-toolbar-tool-table-zero' ) );
									return;
								}
								if ( ( rows * cols ) > 1000 ) {
									// 1000 is in the English message. The parameter replacement is kept for BC.
									// eslint-disable-next-line no-alert
									alert( mw.msg( 'wikieditor-toolbar-tool-table-toomany', 1000 ) );
									return;
								}
								headerText = mw.msg( 'wikieditor-toolbar-tool-table-example-header' );
								normalText = mw.msg( 'wikieditor-toolbar-tool-table-example' );
								table = '';
								for ( r = 0; r < rows + header; r++ ) {
									table += '|-\n';
									for ( c = 0; c < cols; c++ ) {
										isHeader = ( header && r === 0 );
										delim = isHeader ? '!' : '|';
										if ( c > 0 ) {
											delim += delim;
										}
										table += delim + ' ' + ( isHeader ? headerText : normalText ) + ' ';
									}
									// Replace trailing space by newline
									// table[table.length - 1] is read-only
									table = table.substr( 0, table.length - 1 ) + '\n';
								}
								classes = [];
								if ( $( '#wikieditor-toolbar-table-wikitable' ).is( ':checked' ) ) {
									classes.push( 'wikitable' );
								}
								if ( $( '#wikieditor-toolbar-table-sortable' ).is( ':checked' ) ) {
									classes.push( 'sortable' );
								}
								classStr = classes.length > 0 ? ' class="' + classes.join( ' ' ) + '"' : '';
								$( this ).dialog( 'close' );
								$.wikiEditor.modules.toolbar.fn.doAction(
									$( this ).data( 'context' ),
									{
										type: 'replace',
										options: {
											pre: '{|' + classStr + '\n',
											peri: table,
											post: '|}',
											ownline: true
										}
									},
									$( this )
								);

								// Restore form state
								$( '#wikieditor-toolbar-table-dimensions-rows' ).val( 3 );
								$( '#wikieditor-toolbar-table-dimensions-columns' ).val( 3 );
								// Simulate clicks instead of setting values, so the according
								// actions are performed
								if ( !$( '#wikieditor-toolbar-table-dimensions-header' ).is( ':checked' ) ) {
									$( '#wikieditor-toolbar-table-dimensions-header' ).click();
								}
								if ( !$( '#wikieditor-toolbar-table-wikitable' ).is( ':checked' ) ) {
									$( '#wikieditor-toolbar-table-wikitable' ).click();
								}
								if ( $( '#wikieditor-toolbar-table-sortable' ).is( ':checked' ) ) {
									$( '#wikieditor-toolbar-table-sortable' ).click();
								}
							},
							'wikieditor-toolbar-tool-table-cancel': function () {
								$( this ).dialog( 'close' );
							}
						},
						open: function () {
							$( '#wikieditor-toolbar-table-dimensions-rows' ).focus();
							if ( !( $( this ).data( 'dialogkeypressset' ) ) ) {
								$( this ).data( 'dialogkeypressset', true );
								// Execute the action associated with the first button
								// when the user presses Enter
								$( this ).closest( '.ui-dialog' ).keypress( function ( e ) {
									var button;
									if ( ( e.keyCode || e.which ) === 13 ) {
										button = $( this ).data( 'dialogaction' ) || $( this ).find( 'button:first' );
										button.click();
										e.preventDefault();
									}
								} );

								// Make tabbing to a button and pressing
								// Enter do what people expect
								$( this ).closest( '.ui-dialog' ).find( 'button' ).focus( function () {
									$( this ).closest( '.ui-dialog' ).data( 'dialogaction', this );
								} );
							}
						}
					}
				},
				'search-and-replace': {
					browsers: {
						// Left-to-right languages
						ltr: {
							msie: [ [ '>=', 11 ] ] // Known to work on 11.
						},
						// Right-to-left languages
						rtl: {
							msie: [ [ '>=', 11 ] ] // Works on 11 but dialog positioning is cruddy.
						}
					},
					titleMsg: 'wikieditor-toolbar-tool-replace-title',
					id: 'wikieditor-toolbar-replace-dialog',
					htmlTemplate: 'dialogReplace.html',
					init: function () {
						$( this ).find( '[rel]' ).each( function () {
							$( this ).text( mw.msg( $( this ).attr( 'rel' ) ) );
						} );
						// Set tabindexes on form fields
						$.wikiEditor.modules.dialogs.fn.setTabindexes( $( this ).find( 'input' ).not( '[tabindex]' ) );

						// TODO: Find a cleaner way to share this function
						$( this ).data( 'replaceCallback', function ( mode ) {
							var offset, textRemainder, regex, index, i,
								searchStr, replaceStr, flags, matchCase, isRegex,
								$textarea, text, match,
								matchedText, replace, newEnd,
								actualReplacement,
								start, end;

							$( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide();

							// Search string cannot be empty
							searchStr = $( '#wikieditor-toolbar-replace-search' ).val();
							if ( searchStr === '' ) {
								$( '#wikieditor-toolbar-replace-emptysearch' ).show();
								return;
							}

							// Replace string can be empty
							replaceStr = $( '#wikieditor-toolbar-replace-replace' ).val();

							// Prepare the regular expression flags
							flags = 'm';
							matchCase = $( '#wikieditor-toolbar-replace-case' ).is( ':checked' );
							if ( !matchCase ) {
								flags += 'i';
							}
							isRegex = $( '#wikieditor-toolbar-replace-regex' ).is( ':checked' );
							if ( !isRegex ) {
								searchStr = mw.RegExp.escape( searchStr );
							}
							if ( mode === 'replaceAll' ) {
								flags += 'g';
							}

							try {
								regex = new RegExp( searchStr, flags );
							} catch ( e ) {
								$( '#wikieditor-toolbar-replace-invalidregex' )
									.text( mw.msg( 'wikieditor-toolbar-tool-replace-invalidregex',
										e.message ) )
									.show();
								return;
							}

							$textarea = $( this ).data( 'context' ).$textarea;
							text = $textarea.textSelection( 'getContents' );
							match = false;
							if ( mode !== 'replaceAll' ) {
								if ( mode === 'replace' ) {
									offset = $( this ).data( 'matchIndex' );
								} else {
									offset = $( this ).data( 'offset' );
								}
								textRemainder = text.substr( offset );
								match = textRemainder.match( regex );
							}
							if ( !match ) {
								// Search hit BOTTOM, continuing at TOP
								// TODO: Add a "Wrap around" option.
								offset = 0;
								textRemainder = text;
								match = textRemainder.match( regex );
							}

							if ( !match ) {
								$( '#wikieditor-toolbar-replace-nomatch' ).show();
							} else if ( mode === 'replaceAll' ) {
								// Instead of using repetitive .match() calls, we use one .match() call with /g
								// and indexOf() followed by substr() to find the offsets. This is actually
								// faster because our indexOf+substr loop is faster than a match loop, and the
								// /g match is so ridiculously fast that it's negligible.
								// FIXME: Repetitively calling encapsulateSelection() is probably the best strategy
								// in Firefox/Webkit, but in IE replacing the entire content once is better.
								for ( i = 0; i < match.length; i++ ) {
									index = textRemainder.indexOf( match[ i ] );
									if ( index === -1 ) {
										// This shouldn't happen
										break;
									}
									matchedText = textRemainder.substr( index, match[ i ].length );
									textRemainder = textRemainder.substr( index + match[ i ].length );

									start = index + offset;
									end = start + match[ i ].length;
									// Make regex placeholder substitution ($1) work
									replace = isRegex ? matchedText.replace( regex, replaceStr ) : replaceStr;
									newEnd = start + replace.length;
									$textarea
										.textSelection( 'setSelection', { start: start, end: end } )
										.textSelection( 'encapsulateSelection', {
											peri: replace,
											replace: true } )
										.textSelection( 'setSelection', { start: start, end: newEnd } );
									offset = newEnd;
								}
								$( '#wikieditor-toolbar-replace-success' )
									.text( mw.msg( 'wikieditor-toolbar-tool-replace-success', match.length ) )
									.show();
								$( this ).data( 'offset', 0 );
							} else {

								if ( mode === 'replace' ) {

									if ( isRegex ) {
										// If backreferences (like $1) are used, the actual actual replacement string will be different
										actualReplacement = match[ 0 ].replace( regex, replaceStr );
									} else {
										actualReplacement = replaceStr;
									}

									if ( match ) {
										// Do the replacement
										$textarea.textSelection( 'encapsulateSelection', {
											peri: actualReplacement,
											replace: true } );
										// Reload the text after replacement
										text = $textarea.textSelection( 'getContents' );
									}

									// Find the next instance
									offset = offset + match[ 0 ].length + actualReplacement.length;
									textRemainder = text.substr( offset );
									match = textRemainder.match( regex );

									if ( match ) {
										start = offset + match.index;
										end = start + match[ 0 ].length;
									} else {
										// If no new string was found, try searching from the beginning.
										// TODO: Add a "Wrap around" option.
										textRemainder = text;
										match = textRemainder.match( regex );
										if ( match ) {
											start = match.index;
											end = start + match[ 0 ].length;
										} else {
											// Give up
											start = 0;
											end = 0;
										}
									}
								} else {
									start = offset + match.index;
									end = start + match[ 0 ].length;
								}

								$( this ).data( 'matchIndex', start );

								$textarea.textSelection( 'setSelection', {
									start: start,
									end: end } );
								$textarea.textSelection( 'scrollToCaretPosition' );
								$( this ).data( 'offset', end );
								$textarea[ 0 ].focus();
							}
						} );
					},
					dialog: {
						width: 500,
						dialogClass: 'wikiEditor-toolbar-dialog',
						modal: false,
						buttons: {
							'wikieditor-toolbar-tool-replace-button-findnext': function ( e ) {
								$( this ).closest( '.ui-dialog' ).data( 'dialogaction', e.target );
								$( this ).data( 'replaceCallback' ).call( this, 'find' );
							},
							'wikieditor-toolbar-tool-replace-button-replace': function ( e ) {
								$( this ).closest( '.ui-dialog' ).data( 'dialogaction', e.target );
								$( this ).data( 'replaceCallback' ).call( this, 'replace' );
							},
							'wikieditor-toolbar-tool-replace-button-replaceall': function ( e ) {
								$( this ).closest( '.ui-dialog' ).data( 'dialogaction', e.target );
								$( this ).data( 'replaceCallback' ).call( this, 'replaceAll' );
							},
							'wikieditor-toolbar-tool-replace-close': function () {
								$( this ).dialog( 'close' );
							}
						},
						open: function () {
							var dialog, context, textbox,
								that = this;
							$( this ).data( 'offset', 0 );
							$( this ).data( 'matchIndex', 0 );

							$( '#wikieditor-toolbar-replace-search' ).focus();
							$( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide();
							if ( !( $( this ).data( 'onetimeonlystuff' ) ) ) {
								$( this ).data( 'onetimeonlystuff', true );
								// Execute the action associated with the first button
								// when the user presses Enter
								$( this ).closest( '.ui-dialog' ).keypress( function ( e ) {
									var button;
									if ( ( e.keyCode || e.which ) === 13 ) {
										button = $( this ).data( 'dialogaction' ) || $( this ).find( 'button:first' );
										button.click();
										e.preventDefault();
									}
								} );
								// Make tabbing to a button and pressing
								// Enter do what people expect
								$( this ).closest( '.ui-dialog' ).find( 'button' ).focus( function () {
									$( this ).closest( '.ui-dialog' ).data( 'dialogaction', this );
								} );
							}
							dialog = $( this ).closest( '.ui-dialog' );
							that = this;
							context = $( this ).data( 'context' );
							textbox = context.$textarea;

							$( textbox )
								.on( 'keypress.srdialog', function ( e ) {
									var button;
									if ( e.which === 13 ) {
										// Enter
										button = dialog.data( 'dialogaction' ) || dialog.find( 'button:first' );
										button.click();
										e.preventDefault();
									} else if ( e.which === 27 ) {
										// Escape
										$( that ).dialog( 'close' );
									}
								} );
						},
						close: function () {
							var context = $( this ).data( 'context' ),
								textbox = context.$textarea;
							$( textbox ).off( 'keypress.srdialog' );
							$( this ).closest( '.ui-dialog' ).data( 'dialogaction', false );
						}
					}
				}
			} };
		}

	};

}( jQuery, mediaWiki, OO ) );
